The for Loop
The for loop is the oldest and most flexible looping construct in JavaScript. It bundles three pieces of loop bookkeeping — a starting point, a stopping condition, and a step — into a single, readable line. When you need precise control over an index, want to count by twos, or have to walk a structure backwards, the classic for loop is still the right tool.
Anatomy of a for loop
A for loop has three semicolon-separated clauses inside its parentheses, followed by a body:
for (initialization; condition; afterthought) {
// body runs while condition is true
}
Each clause has a distinct job:
| Clause | When it runs | Typical use |
|---|---|---|
| Initialization | Once, before the loop starts | Declare and seed a counter (let i = 0) |
| Condition | Before every iteration | Decide whether to keep going (i < n) |
| Afterthought | After every iteration | Advance the counter (i++) |
Here is a minimal counting loop:
for (let i = 0; i < 5; i++) {
console.log(`iteration ${i}`);
}
Output:
iteration 0
iteration 1
iteration 2
iteration 3
iteration 4
Declare the counter with let, not var. With let, each iteration gets its own binding of i, which matters when you create closures inside the loop. The var version would share a single binding and surprise you.
All three clauses are optional.
for (;;) { ... }is a valid infinite loop — just make sure something insidebreaks out of it.
Looping over arrays by index
The for loop’s headline use is iterating an array when you need the index — for example, to read neighboring elements or to mutate the array in place. Cache the length in a variable so it isn’t re-evaluated on every pass.
const fruits = ["apple", "banana", "cherry"];
for (let i = 0, len = fruits.length; i < len; i++) {
console.log(`${i}: ${fruits[i]}`);
}
Output:
0: apple
1: banana
2: cherry
Because you control the counter directly, the for loop handles patterns that cleaner constructs cannot. Walking backwards is a common one — and it is the safe way to remove items while iterating, since shrinking the array does not skip elements:
const nums = [1, 2, 3, 4, 5, 6];
for (let i = nums.length - 1; i >= 0; i--) {
if (nums[i] % 2 === 0) nums.splice(i, 1);
}
console.log(nums);
Output:
[ 1, 3, 5 ]
Stepping by an amount other than one is just a different afterthought:
for (let i = 0; i <= 10; i += 2) {
console.log(i);
}
Output:
0
2
4
6
8
10
Nested loops
A loop inside another loop is how you process grids, matrices, and pairwise combinations. The inner loop completes a full cycle for every single step of the outer loop.
const grid = [
[1, 2, 3],
[4, 5, 6],
];
for (let row = 0; row < grid.length; row++) {
for (let col = 0; col < grid[row].length; col++) {
process.stdout.write(`${grid[row][col]} `);
}
console.log();
}
Output:
1 2 3
4 5 6
When you nest loops you can target either loop with break and continue by using a label — an identifier placed before the loop. This lets the inner loop break out of the outer one in a single statement:
outer: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i + j === 3) break outer;
console.log(`${i},${j}`);
}
}
Output:
0,0
0,1
0,2
1,0
1,1
Watch the complexity. Two nested loops over the same
n-element collection runn²times. That is fine for small data but quickly becomes a bottleneck — reach for aMaporSetlookup before nesting loops over large arrays.
Common patterns
A few idioms come up again and again:
const scores = [88, 92, 79, 95, 84];
// Accumulate a total
let total = 0;
for (let i = 0; i < scores.length; i++) {
total += scores[i];
}
// Build a fresh array from indices
const labels = [];
for (let i = 0; i < scores.length; i++) {
labels.push(`Q${i + 1}`);
}
console.log(total, labels);
Output:
438 [ 'Q1', 'Q2', 'Q3', 'Q4', 'Q5' ]
For most read-only iteration, modern JavaScript prefers for...of (clean values, no index juggling) or array methods like map, filter, and reduce. Choose the classic for loop when you specifically need the index, a custom step, reverse order, or maximum performance in a hot path.
Best Practices
- Declare loop counters with
letso each iteration gets a fresh binding and closures behave correctly. - Cache
array.lengthin the initialization clause when iterating large arrays in a tight loop. - Iterate backwards when removing elements with
spliceso you do not skip items. - Prefer
for...ofor array methods for plain value iteration; reserveforfor index-driven logic. - Use labeled
break/continueto escape nested loops cleanly instead of flag variables. - Keep loop bodies small; extract complex work into a named function for readability.
- Be wary of nested loops over large collections — consider a
MaporSetto avoidO(n²)cost.