Skip to main content

🔁 Loops (for, while, do-while)

Computers are tireless — their real superpower is doing the same thing thousands of times without complaint. Loops are how you unlock that: run a block repeatedly while a condition holds. This lesson covers the three classic loops, the control statements that steer them, and the modern array methods that increasingly replace them.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Write for, while, and do…while loops and explain when each fits best
  • Trace how the three parts of a for loop — init, condition, update — execute over time
  • Iterate arrays cleanly with for…of and steer loops with break and continue
  • Recognise and prevent infinite loops and off-by-one errors
  • Decide when a built-in array method (map, filter, reduce) reads better than a hand-written loop

Estimated Time: 30–40 minutes  •  Difficulty: Beginner

Hands-on: Implement FizzBuzz and a small receipt calculator, then refactor a loop into a one-line array method.

In This Lesson

Why Loops Matter

A loop executes a block of code repeatedly as long as a condition stays true. Anywhere you'd otherwise copy-paste the same statement — printing every item in a cart, summing a column of numbers, retrying a request — a loop does the job in a few lines and scales to any size of input.

💡 Analogy — an assembly line. A for loop is a line with a counter and a known number of items. A while loop keeps running as long as items keep arriving on the belt. A do…while loop always processes at least one item before checking whether to continue.

JavaScript offers several looping tools. This lesson focuses on the three classic ones, then introduces the array-oriented alternatives you'll reach for most in real code:

flowchart TD A[JavaScript loops] A --> B["for — known number of iterations"] A --> C["while — repeat while a condition holds"] A --> D["do…while — run once, then check"] A --> E["for…of — walk each value in an iterable"] A --> F["array methods — map / filter / reduce"]

The for Loop

The for loop packs three things into one line: an initialization that runs once, a condition checked before every iteration, and an update that runs after every iteration. It's the natural choice when you know — or can compute — how many times to repeat.

// Count 1 to 5
for (let i = 1; i <= 5; i++) {
  console.log(i);
}
// 1 2 3 4 5

// Walk an array by index
const fruits = ["apple", "banana", "orange"];
for (let i = 0; i < fruits.length; i++) {
  console.log(`${i}: ${fruits[i]}`);
}
Execution order of a for loop Initialization runs once; then the condition is checked; if true the body runs and the update runs, looping back to the check; if false the loop ends. 1. init let i = 0 2. condition true? 3. body do the work 4. update i++ end loop true false
Figure 1 — The for loop's lifecycle: init once, then repeat check → body → update until the condition is false.

✅ Real-world example: order receipt

function printReceipt(order) {
  let subtotal = 0;
  for (let i = 0; i < order.items.length; i++) {
    const item = order.items[i];
    const lineTotal = item.price * item.quantity;
    subtotal += lineTotal;
    console.log(`${item.name} x${item.quantity} — $${lineTotal.toFixed(2)}`);
  }
  const tax = subtotal * order.taxRate;
  console.log(`Total: $${(subtotal + tax).toFixed(2)}`);
  return subtotal + tax;
}

The while Loop

A while loop checks its condition before each iteration and runs as long as that condition is true. It shines when you don't know the number of iterations in advance — you loop until something happens.

// Roll a die until we get a 6
let roll = 0;
while (roll !== 6) {
  roll = Math.floor(Math.random() * 6) + 1;
  console.log(`Rolled ${roll}`);
}
console.log("Finally a 6!");
flowchart TD A[start] --> B{condition true?} B -->|yes| C[run body] C --> B B -->|no| D[continue after loop]

⚠️ Every while loop needs an exit

If nothing inside the body can eventually make the condition false, the loop runs forever and freezes the page. Make sure some variable in the condition changes each pass — here, roll is reassigned every iteration.

💡 Analogy — a bouncer. A while loop is a bouncer who checks the rule before letting anyone in. If the club is already full when their shift starts, nobody enters — the body may run zero times.

The do…while Loop

A do…while loop is the mirror image: it runs the body first, then checks the condition. This guarantees the body executes at least once — perfect for prompts and menus where you must ask before you can know whether to repeat.

let count = 10;
do {
  console.log(`count is ${count}`);
  count++;
} while (count < 10);
// Prints "count is 10" once, even though 10 < 10 is false
flowchart TD A[start] --> B[run body] B --> C{condition true?} C -->|yes| B C -->|no| D[continue after loop]
💡 Analogy — try before you buy. You always get the trial once (the body runs), and only then decide whether to keep going (check the condition). A plain while would demand the decision before you ever tried it.

📖 Which loop should I use?

for — a known count or array index. while — repeat until an event, body may run zero times. do…while — repeat until an event, but the body must run at least once.

break, continue & Nested Loops

Two statements steer a loop mid-flight. break exits the loop entirely; continue skips the rest of the current iteration and jumps to the next one.

// break: stop as soon as we find the value
const nums = [3, 7, 2, 9, 4];
let foundAt = -1;
for (let i = 0; i < nums.length; i++) {
  if (nums[i] === 9) { foundAt = i; break; }
}

// continue: skip even numbers, print the odds
for (let i = 1; i <= 10; i++) {
  if (i % 2 === 0) continue;
  console.log(i); // 1 3 5 7 9
}
💡 Analogy — a TV remote. break is "stop" — you're done watching. continue is "skip to next episode" — you drop the current one but keep the series going.

Nested loops

A loop inside a loop walks two dimensions — rows and columns of a grid, for instance. A plain break only exits the inner loop; a labeled break can exit both at once.

const grid = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
];

search: for (let r = 0; r < grid.length; r++) {
  for (let c = 0; c < grid[r].length; c++) {
    if (grid[r][c] === 5) {
      console.log(`Found 5 at [${r}][${c}]`);
      break search; // exits BOTH loops
    }
  }
}

for…of and Array Methods

When you just need each value of an array (and not its index), for…of is cleaner and less error-prone than a counter loop — there's no i to get wrong.

const colors = ["red", "green", "blue"];
for (const color of colors) {
  console.log(color);
}

Better still, many loops are really doing one of three jobs: transforming each item, keeping some items, or combining items into one value. Modern JavaScript expresses those directly, so the intent is obvious at a glance:

GoalLoop approachArray method
Transform each itemfor + pushmap()
Keep matching itemsfor + if + pushfilter()
Combine into one valuefor + accumulatorreduce()
Do a side effect per itemforforEach()
const numbers = [1, 2, 3, 4, 5, 6];

// Hand-written loop
const evensDoubled = [];
for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 === 0) evensDoubled.push(numbers[i] * 2);
}

// Same result, intent-first
const evensDoubled2 = numbers
  .filter(n => n % 2 === 0)
  .map(n => n * 2); // [4, 8, 12]

const sum = numbers.reduce((total, n) => total + n, 0); // 21

💡 Loops aren't obsolete

Array methods are great for transforming data. But a plain loop is still the right tool when you need to break early, mutate an index, or run genuinely imperative steps. Choose the one that makes the code clearest.

Pitfalls & Best Practices

⚠️ Off-by-one errors

Array indexes run from 0 to length - 1. Use < (not <=) with .length, or you'll read one slot past the end and get undefined.

for (let i = 0; i <= arr.length; i++) { /* BUG: overruns by one */ }
for (let i = 0; i <  arr.length; i++) { /* correct */ }

⚠️ Infinite loops

Always ensure the loop can end. Forgetting the update (i++), or a condition that never flips, hangs the tab. If you use while (true), guarantee a break inside.

✅ Do

  • Declare the counter with let, scoped to the loop.
  • Prefer for…of when you don't need the index.
  • break out as soon as you have your answer — don't keep scanning.
  • Reach for map/filter/reduce when they make intent clearer.

Hands-on Exercise

🏋️ FizzBuzz, then Refactor

Objective: Practise a counter loop with branching, then express a loop as an array method.

Instructions:

  1. Write fizzBuzz(n) that returns an array. For each number from 1 to n: push "FizzBuzz" if divisible by both 3 and 5, else "Fizz" if by 3, else "Buzz" if by 5, else the number as a string.
  2. Test fizzBuzz(15) and confirm index 2 is "Fizz" and index 14 is "FizzBuzz".
  3. Refactor challenge: given const prices = [19.99, 4.5, 100, 8], use a single reduce call to compute the total. Then use filter to keep only prices over 10.
💡 Hint

Check the "both" case first — order matters, because 15 is divisible by 3 as well. For the refactor, reduce((sum, p) => sum + p, 0) starts the accumulator at 0.

✅ Sample solution
function fizzBuzz(n) {
  const out = [];
  for (let i = 1; i <= n; i++) {
    if (i % 15 === 0) out.push("FizzBuzz");
    else if (i % 3 === 0) out.push("Fizz");
    else if (i % 5 === 0) out.push("Buzz");
    else out.push(String(i));
  }
  return out;
}

console.log(fizzBuzz(15));
// ["1","2","Fizz","4","Buzz","Fizz","7","8","Fizz","Buzz","11","Fizz","13","14","FizzBuzz"]

const prices = [19.99, 4.5, 100, 8];
const total = prices.reduce((sum, p) => sum + p, 0);   // 132.49
const overTen = prices.filter(p => p > 10);            // [19.99, 100]

🎯 Quick Quiz

Question 1: Which loop is guaranteed to run its body at least once?

Question 2: What does continue do inside a loop?

Question 3: Which array method combines all elements into a single value?

Summary & Quiz

🎉 Key Takeaways

  • for suits a known count; while repeats until an event; do…while is a while that always runs once.
  • A for loop's parts run in the order init → (check → body → update) repeated.
  • break leaves the loop; continue skips to the next iteration; a label lets break/continue target an outer loop.
  • for…of walks values cleanly; map/filter/reduce express transform/select/combine with clear intent.
  • Guard against off-by-one (use < length) and infinite loops (ensure the condition can become false).

📚 Further Reading

🚀 What's Next?

You now have both decisions and repetition — the two halves of control flow. Next we'll combine them into reusable shapes with Common Control Flow Patterns: guard clauses, state machines, filter-map-reduce, and clean error handling.

🎉 Nice work!

Repetition mastered. Time to turn these building blocks into patterns.