Skip to content
JavaScript js control-flow 4 min read

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:

ClauseWhen it runsTypical use
InitializationOnce, before the loop startsDeclare and seed a counter (let i = 0)
ConditionBefore every iterationDecide whether to keep going (i < n)
AfterthoughtAfter every iterationAdvance 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 inside breaks 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 run times. That is fine for small data but quickly becomes a bottleneck — reach for a Map or Set lookup 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 let so each iteration gets a fresh binding and closures behave correctly.
  • Cache array.length in the initialization clause when iterating large arrays in a tight loop.
  • Iterate backwards when removing elements with splice so you do not skip items.
  • Prefer for...of or array methods for plain value iteration; reserve for for index-driven logic.
  • Use labeled break/continue to 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 Map or Set to avoid O(n²) cost.
Last updated June 1, 2026
Was this helpful?