1.2 Loops — Repeating Work in Code
- See what a loop is, and why it beats copying code by hand.
- Write a
forloop — init, condition, update. - Write a
whileloop, and know when to reach for each. - Loop over the items of a list.
- Count and accumulate — a sum, a max, a tally.
- Nest loops, and dodge the two classic pitfalls.
Say you want a program to print "hello" five times. The hard way is
to write it out five times in a row:
console.log("hello");
console.log("hello");
console.log("hello");
console.log("hello");
console.log("hello");
Five lines. Now imagine a thousand. The loop way says it once and repeats it:
for (let i = 0; i < 5; i++) {
console.log("hello");
}
Three lines instead of five — and turning this into a thousand repeats is a
one-character edit, changing the 5. A loop is how we
tell a computer to repeat an instruction as many times as we need, without writing
it out more than once.
A loop is an instruction that runs the same block of code — its body — again and again, until a condition tells it to stop. The loop runs the body, checks whether to keep going, and if so runs it again. Each single pass through the body is one iteration.
Underneath, every loop is answering the same three small questions: where do I
start, should I keep going, and what comes next? A
for loop packs all three into one line; a while loop asks
only the middle one and leaves the rest to you.
The for loop is the workhorse when you know roughly how many passes
you need. It counts with a variable — usually i — that it
initializes once, tests before every pass, and updates after every pass:
for (let i = 0; i < 5; i++) {
console.log(i);
}
Reading it left to right: let i = 0 is the init,
run once before the first pass; i < 5 is the condition,
checked before every pass; and i++ is the update, run
after every pass. This one prints 0, 1, 2, 3, 4 — five iterations,
not six, because the condition fails as soon as i reaches 5.
Tracing a loop by hand — writing down what the counter is, whether the condition holds, and what prints — is the fastest way to catch a mistake before you run anything. Read across each row: check the condition, run the body, then update.
| pass | \(i\) | \(i \le 5\)? | prints |
|---|---|---|---|
| 1 | 1 | yes | 1 |
| 2 | 2 | yes | 2 |
| 3 | 3 | yes | 3 |
| 4 | 4 | yes | 4 |
| 5 | 5 | yes | 5 |
| 6 | 6 | no | — stop |
A while loop drops the counter and just repeats while its condition
holds — no counter required. Use it when the number of passes depends on what
happens inside the loop, like "keep doubling until we pass 100":
let n = 1;
while (n <= 100) {
n = n * 2;
}
console.log(n); // 128
You may loop zero times, or many; nothing about a while loop promises
a fixed count. That gives a simple rule for picking between the two forms. Reach for
for when you know how many times, or you're stepping a counter over a
range or a list — the count is built into the loop header. Reach for
while when you'll stop on a condition, not a count —
"until the user quits," "until the number is small enough."
Two keywords change a loop's flow mid-pass. break leaves the loop
immediately, skipping every remaining iteration:
for (let i = 1; i <= 10; i++) {
if (i === 5) break;
console.log(i); // 1 2 3 4
}
continue is gentler — it skips only the rest of the
current pass and moves on to the next one:
for (let i = 1; i <= 5; i++) {
if (i % 2 === 0) continue;
console.log(i); // 1 3 5
}
The most common job a loop does is visit every item in a list. The counter doubles as the item's position, or index:
let fruits = ["apple", "pear", "plum"];
for (let i = 0; i < fruits.length; i++) {
console.log(i + ": " + fruits[i]);
}
// 0: apple
// 1: pear
// 2: plum
Or, shorter, when you don't need the index at all: for (let f of fruits) { … }
walks each item directly.
A loop counts down from 3 while it stays above 0,
then a line after the loop prints "go!". Trace it in your head first,
then run it to check.
▶ Press Run to see the output…
Solution
The counter starts at 3 and steps down by one each pass, so
it prints 3, 2, 1, go! — a countdown. When i hits
0 the condition i > 0 is false, the loop stops, and only
then does the last line run.
The pattern behind almost every useful loop is to keep a variable outside the loop and grow it on every pass. Start it at a sensible value — zero for a sum, the first item for a maximum, zero for a tally — and let the body do the work.
Add up a list of numbers with a loop.
▶ Press Run to see the output…
Solution
Start an accumulator, total, at \(0\) outside the loop so it
survives every pass, then add each item as the loop visits it:
let nums = [4, 8, 15, 16, 23, 42];
let total = 0;
for (let x of nums) {
total += x;
}
console.log(total); // 108
total lives outside the loop so it survives every pass; the body just
grows it. Sum, product, max, count — all follow this same shape.
Find the largest value in a list with a loop.
▶ Press Run to see the output…
Solution
Guess the first item is biggest, then let the loop correct you:
let nums = [4, 8, 15, 16, 23, 42];
let max = nums[0];
for (let x of nums) {
if (x > max) max = x;
}
console.log(max); // 42
Each pass compares one item to the current champion and updates if it's bigger.
After the last item, max holds the winner.
Count how many items in a list are even.
▶ Press Run to see the output…
Solution
A tally is a sum that only counts the hits — add an if inside
the loop to decide which ones count:
let nums = [4, 8, 15, 16, 23, 42];
let evens = 0;
for (let x of nums) {
if (x % 2 === 0) evens++;
}
console.log(evens); // 4
The loop visits every item; the if decides which ones count. Here
\(4, 8, 16, 42\) are even — four of them.
A loop needs no formula to add up \(1 + 2 + \cdots + 100\) — it just adds one number at a time, in exactly \(100\) steps, and lands on
$$ 1 + 2 + \cdots + 100 = \frac{100 \cdot 101}{2} = 5050. $$That closed form is a nice check that the loop and the arithmetic agree.
A loop trades code length for repetition: three lines can stand in for a thousand.
A loop can sit inside another loop. For each pass of the outer loop, the inner loop runs start to finish — three outer passes times three inner passes is nine lines:
for (let r = 1; r <= 3; r++) {
for (let c = 1; c <= 3; c++) {
console.log(r, c);
}
}
Nested loops are how we handle grids, tables, and pairs. Building each row as a string before printing it makes a multiplication grid:
for (let r = 1; r <= 3; r++) {
let row = "";
for (let c = 1; c <= 3; c++) {
row += (r * c) + " ";
}
console.log(row);
}
// 1 2 3
// 2 4 6
// 3 6 9
Here the outer loop is the row and the inner loop is the columns across that row. The inner count can also depend on the outer counter, not just repeat a fixed number of times — one more star each row prints a triangle:
for (let r = 1; r <= 4; r++) {
console.log("*".repeat(r));
}
// *
// **
// ***
// ****
Row r prints r stars — the shape grows with the
counter.
The triangle above runs rows \(1\) through \(4\), printing \(r\) stars on row \(r\). How many stars does it print in total? The version below also tallies a running total as it goes — run it to check.
▶ Press Run to see the output…
Solution
Row by row that's \(1 + 2 + 3 + 4 = 10\) stars. Extend the same triangle to ten rows and you'd print \(1 + 2 + \cdots + 10 = 55\) — the same running-sum pattern as Worked Example 1.2.1, just counted in stars instead of numbers.
Two mistakes account for most loop bugs. The first is an off-by-one error — running the body one time too many:
let a = [10, 20, 30];
for (let i = 0; i <= a.length; i++) {
console.log(a[i]); // ...30, undefined
}
Indexes are \(0, 1, 2\) — but i <= 3 also tries
a[3], which doesn't exist. The fix is to stop before the
length, not at it:
let a = [10, 20, 30];
for (let i = 0; i < a.length; i++) {
console.log(a[i]); // 10, 20, 30
}
Use <, not <=, when looping to length.
The second mistake is the infinite loop — a condition that
never turns false because nothing inside the loop moves it:
let i = 0;
while (i < 5) {
console.log(i);
} // i stays 0 — forever
The fix is to make progress toward the exit on every pass:
let i = 0;
while (i < 5) {
console.log(i);
i++; // now it reaches 5 and stops
}
Every loop needs a reason to stop: a counter that advances, or a condition that eventually turns false. Forget it and the program hangs.
This loop looks like it should sum \(1\) through \(10\). Does it? Run it and see.
▶ Press Run to see the output…
Solution
Read the condition again: i < 10 never lets i reach
\(10\), so the loop stops at \(9\) and prints \(45\), not \(55\). Fix it with
i <= 10. The same sum-1-to-\(n\) shape from Worked Example 1.2.1
reappears here, and the same off-by-one habit — check whether the boundary
value should be included — catches it.
The fastest way to trust a loop is to run it. The example below is live: the same sum-1-to-\(n\) accumulator from Worked Example 1.2.1, followed by the same triangle from the nested loops above. Change a bound, print a bigger triangle, sum only the odd numbers — then press Run.
▶ Press Run to see the output…
In one breath:
- A loop runs a body again and again until a condition stops it.
forwhen you know the count or step a counter;whilewhen you stop on a condition.- Keep an accumulator outside the loop to sum, find a max, or tally.
- Nest loops for grids and patterns; the inner loop runs fully each outer pass.
- Watch for off-by-one and the infinite loop — always give the loop a way to stop.
A loop trades repetition for a tiny bit of bookkeeping — a counter or a condition. Once you can read the three parts of a loop, most real programs stop looking mysterious. The fastest way to trust a loop is to run it: change a bound, print a bigger triangle, sum only the odd numbers.
You can now write for and while loops, loop over lists,
accumulate results, nest loops, and avoid the classic bugs. Next:
§1.3 Functions — packaging code you can reuse.