How to Write Nested Loops in JavaScript Tutorial

A nested loop is a loop inside another loop. Learn how nested loops work and why the inner loop runs completely for each outer iteration, with grids and multi-dimensional data.

6 min read

A JavaScript nested loop is a loop placed inside the body of another loop. When the outer loop runs once, the inner loop runs all the way through. This creates a multiplication effect: if the outer loop runs N times and the inner loop runs M times, the inner body executes N x M times total.

Nested loops are the natural way to work with grids, tables, matrices, and any data organized in rows and columns.

How a Nested Loop Executes

A nested loop always runs the inner loop to completion for every single pass of the outer loop, which is the one rule that explains all of its behavior. The simplest nested loop pairs two counters:

javascriptjavascript
for (let i = 1; i <= 3; i++) {
  for (let j = 1; j <= 2; j++) {
    console.log(`i=${i}, j=${j}`);
  }
}

The outer loop holds i steady while the inner loop counts fully through j, then the outer loop advances and the inner loop restarts. Output:

texttext
i=1, j=1
i=1, j=2
i=2, j=1
i=2, j=2
i=3, j=1
i=3, j=2

The outer loop runs 3 times. For each outer iteration, the inner loop runs 2 times. Total: 3 x 2 = 6 lines of output.

Nested loop execution order

The diagram shows the exact sequence: the inner loop completes fully before the outer loop advances to its next value. Think of it like a clock -- the second hand (inner) makes a full rotation before the minute hand (outer) ticks forward once.

Counter Naming Convention

By convention, i is the outer counter, j is the first inner counter, and k is the second inner counter:

javascriptjavascript
for (let i = 0; i < 2; i++) {
  for (let j = 0; j < 2; j++) {
    for (let k = 0; k < 2; k++) {
      console.log(i, j, k);
    }
  }
}

This prints all 8 combinations of 0 and 1 across three positions. Using i, j, k makes the nesting level obvious at a glance.

Avoid naming nested loop counters a, b, c or x, y, z. Stick to the convention so other developers can read your code without mental translation.

Printing a Grid

The most practical use of nested loops is generating rows and columns:

javascriptjavascript
const rows = 3;
const cols = 4;
 
for (let r = 0; r < rows; r++) {
  let row = "";
  for (let c = 0; c < cols; c++) {
    row += `[${r},${c}] `;
  }
  console.log(row);
}

The outer loop starts a fresh row string, the inner loop fills in every column of that row, and then the completed row gets printed. Output:

texttext
[0,0] [0,1] [0,2] [0,3]
[1,0] [1,1] [1,2] [1,3]
[2,0] [2,1] [2,2] [2,3]

The outer loop builds one row at a time. The inner loop fills each cell in that row. After the inner loop finishes, the row string is complete and gets printed. Then the outer loop moves to the next row.

Iterating Over a 2D Array

A two-dimensional array is an array of arrays. Nested loops are the standard way to visit every element:

javascriptjavascript
const matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
];
 
for (let i = 0; i < matrix.length; i++) {
  for (let j = 0; j < matrix[i].length; j++) {
    console.log(`matrix[${i}][${j}] = ${matrix[i][j]}`);
  }
}

The outer loop selects a row by index, and the inner loop walks across every column in that row before moving to the next one. Output:

texttext
matrix[0][0] = 1
matrix[0][1] = 2
matrix[0][2] = 3
matrix[1][0] = 4
matrix[1][1] = 5
matrix[1][2] = 6
matrix[2][0] = 7
matrix[2][1] = 8
matrix[2][2] = 9

Notice matrix[i].length in the inner condition. Each row could have a different number of columns, so the inner loop uses the current row's length rather than a fixed value. This is safer than assuming every row has the same width.

Finding All Pairs

Nested loops are the go-to tool for comparing every item against every other item:

javascriptjavascript
const colors = ["red", "green", "blue"];
 
for (let i = 0; i < colors.length; i++) {
  for (let j = i + 1; j < colors.length; j++) {
    console.log(`${colors[i]} + ${colors[j]}`);
  }
}

The inner loop always starts one position ahead of the outer loop, so every pair is compared exactly once and nothing is paired with itself. Output:

texttext
red + green
red + blue
green + blue

Starting the inner loop at j = i + 1 avoids duplicate pairs like green-plus-red after red-plus-green, and avoids pairing an item with itself. This pattern shows up in comparison logic, combination generation, and any task where order does not matter.

Breaking Out of Nested Loops

A plain break only exits the innermost loop:

javascriptjavascript
for (let i = 1; i <= 3; i++) {
  for (let j = 1; j <= 3; j++) {
    if (j === 2) break;
    console.log(i, j);
  }
}

The break only stops the inner loop each time it fires, so the outer loop keeps advancing through all three of its own values. Output:

texttext
1 1
2 1
3 1

The inner loop stops as soon as j reaches 2, but the outer loop keeps running. To stop the outer loop from inside the inner loop, use a labeled statement:

javascriptjavascript
outerLoop: for (let i = 1; i <= 3; i++) {
  for (let j = 1; j <= 3; j++) {
    if (i === 2 && j === 1) break outerLoop;
    console.log(i, j);
  }
}

Because the label targets the outer loop, the break stops both loops together the moment the condition matches, instead of letting the outer loop continue. Output:

texttext
1 1
1 2
1 3

The label outerLoop: marks the outer for loop. When break outerLoop runs, both loops stop immediately. Use labels sparingly -- they are powerful but can make control flow hard to follow. For more on break, see the break statement guide.

Common Mistakes

Using the same counter variable for both loops. Each nesting level needs its own counter:

javascriptjavascript
// WRONG: uses i for both loops
for (let i = 0; i < 3; i++) {
  for (let i = 0; i < 3; i++) { // Shadows outer i
    console.log(i); // Only sees inner i
  }
}

Accidentally writing an N-squared algorithm. A nested loop over an array of length N runs N x N iterations. For large N, this can be slow.

Before writing a nested loop, ask: could I use a Map or Set instead? Could I sort first and use two pointers? Not every pair problem needs a nested loop.

Forgetting to reset the inner state. If the inner loop builds up a value, make sure it starts fresh for each outer iteration:

javascriptjavascript
// RIGHT: row resets for each outer iteration
for (let r = 0; r < 3; r++) {
  let row = ""; // Fresh start each row
  for (let c = 0; c < 3; c++) {
    row += "*";
  }
  console.log(row);
}

If the row variable were declared outside both loops instead of reset inside the outer one, each row would append to the previous one instead of starting fresh.

For more on how the for loop syntax works, and how to loop through arrays with a single loop, see the linked guide.

Rune AI

Rune AI

Key Insights

  • A nested loop is a loop placed inside the body of another loop.
  • The inner loop runs to completion for each single iteration of the outer loop.
  • Use i for the outer counter and j for the inner counter as a convention.
  • Nested loops are essential for grids, tables, matrices, and pattern generation.
  • Break only exits the innermost loop; use labels to break outer loops.
RunePowered by Rune AI

Frequently Asked Questions

How many levels of nesting can I use?

Technically there is no limit, but more than three levels of nesting becomes very hard to read and debug. If you find yourself nesting deeper than three levels, consider breaking the logic into functions or using a different data structure.

Do nested loops make my code slow?

Yes. A nested loop of size N times M runs N x M total iterations. For large arrays, this can be slow. Always check whether you really need a nested loop, or whether a Map, Set, or different algorithm would work better.

Can I use break inside a nested loop?

Yes, but break only exits the innermost loop. To break out of an outer loop from inside an inner loop, use a labeled statement.

Conclusion

Nested loops are the standard way to work with grids, tables, and multi-dimensional data in JavaScript. The key rule is simple: the inner loop runs completely for every single iteration of the outer loop. Keep nesting shallow, name your counters clearly, and always check whether a nested loop is the right tool before reaching for one.