🔁 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
forloop — 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. Aforloop is a line with a counter and a known number of items. Awhileloop keeps running as long as items keep arriving on the belt. Ado…whileloop 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:
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]}`);
}
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!");
⚠️ 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
💡 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.breakis "stop" — you're done watching.continueis "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:
| Goal | Loop approach | Array method |
|---|---|---|
| Transform each item | for + push | map() |
| Keep matching items | for + if + push | filter() |
| Combine into one value | for + accumulator | reduce() |
| Do a side effect per item | for | forEach() |
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…ofwhen you don't need the index. breakout as soon as you have your answer — don't keep scanning.- Reach for
map/filter/reducewhen 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:
- Write
fizzBuzz(n)that returns an array. For each number from 1 ton: 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. - Test
fizzBuzz(15)and confirm index 2 is"Fizz"and index 14 is"FizzBuzz". - Refactor challenge: given
const prices = [19.99, 4.5, 100, 8], use a singlereducecall to compute the total. Then usefilterto 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
forloop'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
- MDN — Loops and iteration
- MDN — for statement
- MDN — Array.prototype.reduce()
- JavaScript.info — Loops: while and for
🚀 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.