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, likelet i = 0in aforloop. - 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 (let i = 1; i <= 5; i++) {
console.log("Step", i);
}
// Step 1 ... Step 502Follow 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.
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.
let money = 100;
let years = 0;
while (money < 200) {
money = money * 1.1; // 10% growth
years++;
}
console.log(years); // 804Run 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.
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.
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
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
Predict the doublings
In the second example, change
1000to100. Predict the power and the number of doublings, then run. (128 after 7 doublings.) - 3
Grow the table
In the last example, set
sizeto12. Predict the cell count before running.
Code & diagrams
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
Console
Nothing logged yet.Expected console output
10 8 6 4 2 0
1 + 2 + ... + 100 = 5050Same 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 = 10Index 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 cherryThe 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
Console
Nothing logged yet.Expected console output
cells: 25Break 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.)
Break #2
Stop a loop with an exact decimal
Write for (let x = 0; x !== 1; x += 0.1) { ... }.
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
setTimeoutorscheduler.yield()where supported) or move them into a Web Worker (you'll meet timers and the event loop in Phase 10). - ▸
With
letin aforheader, 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 owni, unlikevar(Topic 3.6). Engines optimise this away when nothing capturesi. - ▸
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
/dsacourse.
Remember this
- 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
The **
forloop** puts all three parts in one line:for (let i = 0; i < 5; i++) { ... }. Read it as: "start withiat 0; before each iteration, checki < 5; after each iteration, runi++". So the body runs withi= 0, 1, 2, 3, 4 (five times) and stops whenireaches 5. The variablei(short for index) is called the loop counter. Declared withlet, 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
The **
whileloop** 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
The **
do...whileloop 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 afterwhile (...). It's the least common of the three loops. - 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<=readsarr[3], which isundefined(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
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
/dsacourse. For walking through arrays and other lists,for...of(Topic 2.7) is usually clearer than a counting loop, and array methods likemapandfilter(Phase 5) are clearer still.
Explain it without notes
Describe the three parts of a for loop and the order in which they run.
When would you choose while over for, and do...while over while?
What is an off-by-one error? Give an example and how to avoid it.
Why does an infinite loop freeze the whole page?
Practice
Print the numbers from 10 down to 1, then Lift off!.
Using a while loop, find how many times you can halve 1000 (with / 2) before it is less than 1.
Print a staircase of # signs with 4 rows: #, ##, ###, ####.
Trade-offs
- ↔
A counting
forloop gives full control (index, direction, step) but more room for off-by-one bugs;for...ofand 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,whileanddo...whileloops 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.