Command Palette

Search for a command to run...

Hectal
PHASE 2Beginner ~27 min· topic 5 of 7

Topic 2.5

Loops: for, while and do...while

In one line

Loops repeat a block of code: for when you know how to count the repeats, while when you repeat until a condition changes, and do...while when the body must run at least once. Most loop bugs are off-by-one errors and conditions that never become false.

Think of it like this

Climbing stairs. A for loop is "climb 10 steps, counting as you go". A while loop is "keep climbing while you can't see the top yet": you don't know how many steps in advance. A do...while loop is "take one step, then check whether to keep going": you always take at least one step. And if you count wrong, you either stop one step short or step off into the air: that's the off-by-one error.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Loop
Code that runs a block again and again while a condition is true.
Iteration
One run of the loop's body.
Loop counter
A variable (often i) that counts iterations, like let i = 0 in a for loop.
Condition
The test checked before (or after) each iteration that decides whether to keep going.
Infinite loop
A loop whose condition never becomes false, so it never stops and the page freezes.
Off-by-one error
A loop that runs one time too many or one too few, usually from <= vs < or starting at 1 instead of 0.
Nested loop
A loop inside another loop. The inner one runs completely for every iteration of the outer one.
Index
The position of an item in an array, starting from 0.

Step by step

01Count with for

The three parts sit in the parentheses, separated by semicolons: start; condition; step.

for.jswhole filejavascript
for (let i = 1; i <= 5; i++) {
  console.log("Step", i);
}
// Step 1 ... Step 5

02Follow the order of the parts

The start runs once. Then: check the condition, run the body, run the step, check again... The step runs after the body, and the loop ends the moment the check is false.

Follow the order of the partsdiagram
Rendering diagram…

03Repeat until something changes with while

Set up before, check at the top, change inside. Here we don't know how many times we'll double, only when to stop.

while.jswhole filejavascript
let money = 100;
let years = 0;
while (money < 200) {
  money = money * 1.1; // 10% growth
  years++;
}
console.log(years); // 8

04Run at least once with do...while

The check is at the bottom, so the body always runs once. The only difference from while is when the condition is checked.

do-while.jswhole filejavascript
let attempts = 0;
do {
  attempts++;
  console.log("Attempt", attempts);
} while (attempts < 3);

05Check the edges

For an array of length n, valid indexes are 0 to n - 1. So start at 0 and continue while i < n. Counting down? Start at n - 1 and continue while i >= 0.

edges.jswhole filejavascript
const letters = ["a", "b", "c"];
for (let i = letters.length - 1; i >= 0; i--) {
  console.log(letters[i]); // c, b, a
}

06Know what nesting costs

The inner loop runs fully for every outer iteration: 10 rows × 10 columns is 100 iterations. Double the size and the work is four times bigger.

Try it yourself

  1. 1

    Count by threes

    In the first example, change the second loop so it prints 0 3 6 9 12 15. Which parts did you change: start, condition or step?

  2. 2

    Predict the doublings

    In the second example, change 1000 to 100. Predict the power and the number of doublings, then run. (128 after 7 doublings.)

  3. 3

    Grow the table

    In the last example, set size to 12. Predict the cell count before running.

Code & diagrams

for: counting, stepping and summing New tab

The step can be anything: `i++`, `n -= 2`, `i *= 2`. The loop builds five list items on the page, then counts down by twos.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

10 8 6 4 2 0
1 + 2 + ... + 100 = 5050
while and do...while New tab

Same false condition, different result: `while` skips its body, `do...while` runs it once.

Console

(nothing was printed)

Expected console output

2024 has 4 digits
first power of 2 above 1000: 1024 after 10 doublings
do...while body ran with b = 10
Off-by-one: < vs <= New tab

Index 3 doesn't exist, and reading it gives `undefined` instead of an error, so this bug is easy to miss.

Console

(nothing was printed)

Expected console output

With <=:
0 apple
1 banana
2 cherry
3 undefined
With <:
0 apple
1 banana
2 cherry
Nested loops: a times table New tab

The inner loop runs 5 times for each of the 5 rows: 5 × 5 = 25. Set `size` to 10 and it becomes 100: four times the work for twice the size.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

cells: 25

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Forget the step

In the second example, delete the line n = Math.floor(n / 10); from the first while loop. (Save your work first: the tab will freeze.)

terminal
$ Browser
── what you'll see ──
The page stops responding; after a while the browser offers "Page unresponsive: Wait / Exit page".

Break #2

Stop a loop with an exact decimal

Write for (let x = 0; x !== 1; x += 0.1) { ... }.

terminal
$ Browser
── what you'll see ──
The loop never ends: x goes 0.1, 0.2, 0.30000000000000004, ... and jumps past 1 without ever equalling it.

Myth vs fact

Myth

while and do...while are the same.

Fact

while checks before the body and may run zero times; do...while checks after and always runs at least once.

Myth

Reading past the end of an array throws an error.

Fact

It quietly gives undefined. That's why off-by-one bugs in JavaScript are silent and easy to miss.

Myth

Caching arr.length in a variable makes loops much faster.

Fact

Modern engines optimise i < arr.length well. It hasn't been a meaningful win for years; write the clear version.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    A long-running loop blocks the main thread, which also handles input and rendering. Browsers aim for a frame every ~16 ms (60 fps); any task over 50 ms counts as a long task in DevTools' Performance panel. Split heavy loops into chunks (with setTimeout or scheduler.yield() where supported) or move them into a Web Worker (you'll meet timers and the event loop in Phase 10).

  • ▸

    With let in a for header, the spec creates a fresh binding for every iteration and copies the value across (CreatePerIterationEnvironment). That's why callbacks created in the loop each see their own i, unlike var (Topic 3.6). Engines optimise this away when nothing captures i.

  • ▸

    Engines perform on-stack replacement (OSR): a long loop that started in the interpreter can switch to optimised machine code in the middle of running. That's why a single hot loop gets fast even if its function is only called once.

  • ▸

    Counting loops are still the right tool when you need the index, step by more than one, walk backwards, or walk two positions at once (two pointers). Those patterns are taught in Hectal's /dsa course.

Remember this

  1. 1

    A loop runs the same block of code many times. Each run is called an iteration. Every loop needs three things: a start (where we begin), a condition (checked to decide whether to keep going) and a step (something that changes, so the condition eventually becomes false). If nothing changes, the condition stays true forever and you get an infinite loop: the page freezes, because JavaScript in a page runs on one thread and nothing else (clicks, painting) can happen until the loop ends.

  2. 2

    The **for loop** puts all three parts in one line: for (let i = 0; i < 5; i++) { ... }. Read it as: "start with i at 0; before each iteration, check i < 5; after each iteration, run i++". So the body runs with i = 0, 1, 2, 3, 4 (five times) and stops when i reaches 5. The variable i (short for index) is called the loop counter. Declared with let, it exists only inside the loop, and each iteration gets its own copy (important for closures, Topic 3.6). All three parts are optional: for (;;) is an infinite loop.

  3. 3

    The **while loop** has only a condition: while (condition) { ... }. It checks the condition before every iteration, so if the condition is false at the start, the body never runs. You set up the starting value before the loop and change it inside the body. Use it when you don't know the number of repeats in advance: keep halving a number until it's below 1, keep reading until there is no more input, keep retrying until it works.

  4. 4

    The **do...while loop checks the condition after** each iteration: do { ... } while (condition);. So the body always runs at least once, even if the condition is false from the start. Use it for "do something, then decide whether to repeat", such as showing a menu once and repeating while the user picks "again". Note the semicolon after while (...). It's the least common of the three loops.

  5. 5

    An off-by-one error is a loop that runs one time too many or too few. Array positions (indexes) start at 0, so an array of length 3 has indexes 0, 1 and 2. The standard loop is for (let i = 0; i < arr.length; i++) with <, not <=. Using <= reads arr[3], which is undefined (JavaScript doesn't throw for a missing index, so the bug is silent). Ask two questions about every loop: "what is the first value?" and "what is the last value?", and check the edges.

  6. 6

    Loops are where performance starts to matter. A loop inside a loop (nested loops) multiplies the work: 1,000 × 1,000 is a million iterations. How the amount of work grows with the input size is called time complexity, and it's the heart of Hectal's /dsa course. For walking through arrays and other lists, for...of (Topic 2.7) is usually clearer than a counting loop, and array methods like map and filter (Phase 5) are clearer still.

Explain it without notes

01

Describe the three parts of a for loop and the order in which they run.

02

When would you choose while over for, and do...while over while?

03

What is an off-by-one error? Give an example and how to avoid it.

04

Why does an infinite loop freeze the whole page?

Practice

01

Print the numbers from 10 down to 1, then Lift off!.

02

Using a while loop, find how many times you can halve 1000 (with / 2) before it is less than 1.

03

Print a staircase of # signs with 4 rows: #, ##, ###, ####.

Trade-offs

  • ↔

    A counting for loop gives full control (index, direction, step) but more room for off-by-one bugs; for...of and array methods remove the counter and its bugs when you only need the items.

  • ↔

    Long loops on the main thread are simple but freeze the page; chunking or a Web Worker keeps the page responsive at the cost of more complex code.

Done when you can

  • I can write for, while and do...while loops and explain how they differ.

  • I can trace the order: init, condition, body, update.

  • I can spot and fix off-by-one errors with < and zero-based indexes.

  • I can make sure every loop moves toward its end.

  • I can estimate how much work nested loops do.