Say you want to print the numbers 1 to 100. You could write console.log(1), then console.log(2), and keep going for 98 more lines. Or you could write three lines that tell JavaScript: "do this again, and again, until I say stop." That's a loop. This page shows you exactly what happens on every lap of a loop, step by step, and the tiny mistake (being off by one) that trips up beginners and experts alike.
Here is the most common loop in JavaScript, the for loop. It prints a greeting three times:
for (let i = 0; i < 3; i++) {
console.log("Hello #" + i);
}
// Hello #0
// Hello #1
// Hello #2
(console.log is JavaScript's way of printing a line of text. You can try any of this in your browser: press F12 and open the Console tab.)
The code between the curly braces { } is the loop's body: the part that gets repeated. Each run through the body is called an iteration, or a lap. The variable i is the counter. It's just an ordinary variable, and the name i (short for "index") is a tradition, not a rule. You could call it lap or n.
The line that starts with for has three parts inside the brackets, separated by semicolons:
let i = 0i < 3true, run the body. If false, the loop is over.i++i++ is shorthand for i = i + 1: add one to the counter.The order is the part people get wrong in their heads, so step through it and watch. The orange box shows which piece of the code JavaScript is running right now.
Loop stepper
Notice the rhythm: check, body, update, check, body, update… The condition is asked one more time than the body runs. With i < 3, the body runs for 0, 1 and 2, and then the fourth check (3 < 3) is false, which ends the loop. That last "no" is what stops it, and it means the counter finishes at 3, a value the body never saw.
A while loop is the same idea with the parts spread out. It only has the condition in its brackets. You do the setup before the loop and the update inside the body yourself:
let n = 3; // setup
while (n > 0) { // condition
console.log(n);
n--; // update: n = n - 1
}
console.log("Liftoff!");
// 3, 2, 1, Liftoff!
Pick the while loop tab in the stepper above to see it run. Every for loop can be rewritten as a while loop and the other way round. So which one should you use?
for when you know how many laps you want: "10 times", "once per item in this list". Having setup, condition and update on one line makes the loop easy to read at a glance.while when you don't know in advance: "keep asking until the password is right", "keep rolling the dice until you get a six".The infinite loop. If the condition never becomes false, the loop never ends. Delete the n--; line above and n stays 3 forever, so 3 > 0 is always true. Your browser tab freezes, and after a while it offers to stop the page. Every loop needs something in it that moves the counter toward the finish line.
An off-by-one error is when a loop runs one time too many or one time too few. It's so common it has its own name, and it almost always comes from two facts that pull against each other.
Fact one: JavaScript counts positions in a list (an array) starting from 0. In ["kiwi", "fig", "plum"], "kiwi" is at position (or index) 0 and "plum" is at index 2.
Fact two: the array's .length is how many items it has, which is 3. So the last index is always length - 1, never length.
Now look at this loop. Its author wanted to print every fruit:
const fruits = ["kiwi", "fig", "plum"];
for (let i = 0; i <= fruits.length; i++) {
console.log(fruits[i]);
}
// kiwi
// fig
// plum
// undefined ← one lap too many
The <= ("less than or equal to") lets i reach 3, and fruits[3] doesn't exist. JavaScript doesn't stop with an error here; it quietly hands back undefined, its value for "nothing here". That makes the bug easy to miss. Step through the off-by-one bug tab above and watch the moment it happens.
The fix is one character: i < fruits.length. A handy rule for counting laps:
for (let i = a; i < b; i++) runs b − a times. From 0 with < 3: 3 laps (0, 1, 2).for (let i = a; i <= b; i++) runs b − a + 1 times. From 1 with <= 3: 3 laps (1, 2, 3).Both are fine. What matters is that the start and the comparison match each other. The two safe habits: start at 0 and use < (perfect for arrays), or start at 1 and use <= (good for "count from 1 to 10"). Mixing them, 0 with <= or 1 with <, is where off-by-one bugs come from.
Change the start, end and step, and flip the comparison. The playground runs the loop for you and shows every value i takes, plus the value that made the condition false (the dashed red one). Try the presets, especially the last three.
Loop playground
A few things worth noticing in the playground:
< to <= adds exactly one lap whenever the counter lands right on the end value. That single extra lap is the whole off-by-one bug.i += 2 means "add 2 each time", which gives you every other number. A negative step counts down, but then you need > or >=, or the loop will either never start or never stop.Predict the answer first, then click.
How many times does this print "hi"?
for (let i = 0; i < 4; i++) {
console.log("hi");
}
The body runs for i = 0, 1, 2 and 3. Then 4 < 4 is false. Starting at 0 with <, the lap count is simply the end number.
What is the last number printed?
for (let i = 1; i <= 10; i += 3) {
console.log(i);
}
i goes 1, 4, 7, 10, and 10 <= 10 is true, so 10 is printed. Next i becomes 13, 13 <= 10 is false, and the loop ends without printing 13.
What does this print?
let x = 5;
while (x > 5) {
console.log(x);
x--;
}
The condition is checked before the first lap. 5 > 5 is false, so the body never runs. To include 5, the condition would need x >= 5, or more likely x > 0.
What does this print?
const colors = ["red", "green"];
console.log(colors[colors.length]);
colors.length is 2, but the indexes are 0 and 1. There's nothing at index 2, and JavaScript returns undefined rather than an error. The last item is colors[colors.length - 1].
for loop has three parts: setup (once), condition (before every lap), update (after every lap).while loop only has the condition. Setup goes before it, and the update goes inside the body. Forget the update and you get an infinite loop.false is what stops the loop.length - 1. Use i < array.length, not <=.< runs end − start times, <= runs one more.Next time a loop prints one item too many, or one too few, you'll know where to look first: the start value and the comparison sign.