The for loop is the workhorse of repetition. It packs the start, the condition, and the change into one line, so you can see the whole shape of the loop at a glance. When you know how many times something should happen, for is usually the right tool.
for (exp1; exp2; exp3) {
// code block to be executed
}
| Part | Job | Runs |
|---|---|---|
| exp1 (start) | Sets up the counter: let i = 0 | Once, before anything else |
| exp2 (condition) | Decides whether to run the block: i < 5 | Before every pass |
| exp3 (change) | Moves the counter along: i++ | After every pass |
The three parts are separated by semicolons, not commas.
for (let i = 0; i < 3; i++) {
console.log("Pass " + i);
}
let i = 0 runs once, before anything elsei < 3 is checked. If it is false, the loop ends immediatelyi++ runs, then JavaScript jumps back to step 2Tracing it by hand: i is 0 (print), 1 (print), 2 (print), 3 (condition fails, stop). The body ran three times. Tracing a loop on paper like this is the best way to predict what it will do.
The change step does not have to be i++. It can count down, count by twos, or anything else that moves toward the end:
// Count down
for (let i = 3; i > 0; i--) {
console.log(i);
}
console.log("Liftoff");
// Count by twos
for (let i = 0; i <= 10; i += 2) {
console.log(i);
}
const cars = ["BMW", "Volvo", "Saab", "Ford"];
let text = "";
for (let i = 0; i < cars.length; i++) {
text += cars[i] + " ";
}
console.log(text);
Loops often accumulate a value: a running total, a string, a count.
const prices = [4.50, 12.00, 3.25];
let total = 0;
for (let i = 0; i < prices.length; i++) {
total += prices[i];
}
console.log("Total: $" + total.toFixed(2));
total) must be declared outside the loop. Declared inside, it would be reset to 0 on every pass and you would only ever see the last price.
Each of the three expressions can be left out, as long as the semicolons stay. You will not do this often, but you will see it.
Leave out exp1 when the counter is already set before the loop:
let i = 2;
let text = "";
for (; i < cars.length; i++) {
text += cars[i] + " ";
}
console.log(text); // starts from index 2
Leave out exp3 when the counter changes inside the body instead:
let i = 0;
for (; i < cars.length; ) {
console.log(cars[i]);
i++;
}
That is really a while loop wearing a for costume, and the next lesson shows the cleaner way to write it.
Leave out exp2 and there is no condition at all, so the loop never stops on its own. You must use break inside it (see JS Break), or the page freezes.
Declare the counter with let, and it belongs to the loop only. Declare it with the old var, and it leaks out and overwrites any variable of the same name:
var i = 5;
for (var i = 0; i < 10; i++) {
// some code
}
console.log(i); // var: the loop redeclared the outer i
let j = 5;
for (let j = 0; j < 10; j++) {
// some code
}
console.log(j); // let: the outer j is untouched
This is the block scope rule from JS Let, applied to loops. Use let.
for (let i = 0; i < 5; i++); { count++; } has a semicolon after the ). That semicolon is the loop's entire body: an empty statement, repeated 5 times. The block in braces is then an ordinary block that runs once, after the loop. No error, just a wrong answer. (It is the same trap as the stray semicolon after an if.)
When you only need each value and not its position, for...of is cleaner. There is no counter and no index to get wrong:
const colors = ["red", "green", "blue"];
for (const color of colors) {
console.log(color);
}
Each pass, color holds the next value. It can be const because it is a fresh variable on every pass, never reassigned. It works on strings too, one character at a time:
for (const letter of "JS") {
console.log(letter);
}
An object stores values under names, like {name: "Alex", level: 4} (objects get their own section later). for...in visits each name (called a key):
const player = { name: "Alex", level: 4, hp: 80 };
for (const key in player) {
console.log(key + " = " + player[key]);
}
for...in for objects only. On an array it gives you the index positions (as strings: "0", "1"...), not the values. Memory trick: of gives values of a list; in gives the keys in an object.
A loop inside a loop runs the inner loop completely for each single pass of the outer one. With 3 outer passes and 3 inner passes, the inner body runs 9 times:
for (let row = 1; row <= 3; row++) {
let line = "";
for (let col = 1; col <= 3; col++) {
line += (row * col) + "\t";
}
console.log(line);
}
"\t" is a tab character, used here to line the columns up. Each loop needs its own counter name (row and col); reusing i for both would make the inner counter hide the outer one, and the table would come out wrong.
What does this log?
for (let i = 10; i > 0; i -= 3) {
console.log(i);
}
10, 7, 4, 1. Next would be -2, which fails i > 0, so the loop stops.Which header is written correctly?
for (let i = 0, i < 5, i++)for (let i = 0; i < 5; i++)for let i = 0; i < 5; i++for (i < 5; let i = 0; i++)You want to print every value in const pets = ["cat", "dog"]; and don't need the index. Which loop is the cleanest fit?
for (const p in pets)for (const p of pets)while (pets)for (let i = 0; i <= pets.length; i++)for...of hands you each value directly. A gives the indexes ("0", "1"), C never ends (an array is always truthy), and D runs one time too many because of <=.for...of