for loop
The classic C-style for loop is the oldest and most general loop in JavaScript. It gives you full control over three things: where the loop starts, when it stops, and how the counter changes on each pass. Most of the time you'll prefer forEach, for...of or array methods — but when you need that control, nothing else fits.
The three-part header
A for loop has three semicolon-separated parts inside its parentheses:
for (let i = 0; i < 5; i++) {
console.log(i);
}0 1 2 3 4
Init (
let i = 0) runs once before the loop begins.Condition (
i < 5) is checked before each iteration. If it is falsy, the loop ends.Update (
i++) runs after each iteration, before the condition is checked again.
Each part is optional. for (;;) { ... } is a perfectly legal infinite loop — handy when you intend to break out from inside.
Iterating an array
The textbook use case: walk an array by index.
const words = ["alpha", "beta", "gamma"];
for (let i = 0; i < words.length; i++) {
console.log(i, words[i]);
}0 alpha 1 beta 2 gamma
Notice the cache opportunity: if words.length is expensive (it isn't on a plain array, but it can be on a DOM NodeList), hoist it out: for (let i = 0, n = words.length; i < n; i++).
Counting backwards and by steps
The update expression doesn't have to be i++. Anything that converges towards the condition works.
Backwards
for (let i = 10; i > 0; i--) {
console.log(i);
}By twos
for (let i = 0; i < 20; i += 2) {
console.log(i);
}Two counters at once
for (let left = 0, right = arr.length - 1; left < right; left++, right--) {
// converge from both ends — useful for in-place reversals, palindrome checks
}Scoping pitfalls with var
The single biggest reason let exists is the loop. var is function-scoped, so every iteration shares the same counter — which destroys closures created inside the loop.
var — all three timeouts share one i
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log("var:", i), 100);
}
// var: 3
// var: 3
// var: 3let — each iteration gets a fresh i
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log("let:", i), 100);
}
// let: 0
// let: 1
// let: 2break and continue
break ends the loop immediately. continue skips the rest of the current iteration and goes to the update step.
for (let i = 0; i < 10; i++) {
if (i === 5) break; // stop the whole loop at 5
if (i % 2 === 0) continue; // skip even numbers
console.log(i);
}1 3
When to choose for over forEach
Array methods like forEach are usually nicer to read, but for still wins in a few situations:
You need to break out early —
forEachcannot break;for,for...ofandsome/everycan.You need the index and you also need to skip ahead (
i += 2,i -= 1) —forEachalways increments by one.You are walking multiple arrays in lockstep with the same index.
You are in a tight performance loop over millions of elements —
foris reliably the fastest, though the difference rarely matters.
Early exit — for wins
function indexOf(arr, target) {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === target) return i;
}
return -1;
}Nested loops
Looping inside a loop is fine — but the cost is multiplicative. A 1000×1000 nested loop is a million iterations, which is the boundary where you should ask if a different data structure (Set, Map) would let you flatten it.
const grid = [
[1, 2, 3],
[4, 5, 6],
];
for (let r = 0; r < grid.length; r++) {
for (let c = 0; c < grid[r].length; c++) {
console.log(`[${r}][${c}] = ${grid[r][c]}`);
}
}Infinite loops on purpose
for (;;) { ... break; } is the canonical way to say "loop forever until I say stop". It is functionally identical to while (true); pick whichever your team finds clearer.
for (;;) {
const chunk = readNextChunk();
if (chunk === null) break;
process(chunk);
}