How to Write Nested Loops in JavaScript: Tutorial
Learn how to write nested loops in JavaScript with practical examples. Covers nested for loops, grid processing, matrix operations, pattern printing, performance considerations, and when to flatten nested structures.
A nested loop is a loop inside another loop. The inner loop runs completely for every single iteration of the outer loop. If the outer loop runs 5 times and the inner loop runs 10 times, the inner body executes 50 times total. Nested loops are essential for working with two-dimensional data: grids, matrices, tables, combinations, and any structure where you need to process rows and columns.
This tutorial covers nested for loops, practical examples with grids and matrices, using break and continue in nested contexts, performance implications, and guidelines for when to avoid nesting.
Basic Nested Loop Structure
for (let outer = 0; outer < 3; outer++) {
for (let inner = 0; inner < 4; inner++) {
console.log(`outer: ${outer}, inner: ${inner}`);
}
}The inner loop runs 4 full iterations for each of the 3 outer iterations, producing 12 total log statements:
| Outer | Inner iterations | Outputs |
|---|---|---|
| 0 | 0, 1, 2, 3 | (0,0) (0,1) (0,2) (0,3) |
| 1 | 0, 1, 2, 3 | (1,0) (1,1) (1,2) (1,3) |
| 2 | 0, 1, 2, 3 | (2,0) (2,1) (2,2) (2,3) |
Total body executions: 3 x 4 = 12
Name Your Counters
Avoid generic i and j in nested loops. Use meaningful names like row and col, student and grade, or dept and team. Clear names prevent bugs where you accidentally use the wrong counter variable.
Processing a 2D Array (Matrix)
The most common use case for nested loops is iterating over rows and columns of a 2D array:
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
for (let row = 0; row < matrix.length; row++) {
for (let col = 0; col < matrix[row].length; col++) {
console.log(`[${row}][${col}] = ${matrix[row][col]}`);
}
}Summing All Elements
const grid = [
[10, 20, 30],
[40, 50, 60],
[70, 80, 90],
];
let total = 0;
for (let row = 0; row < grid.length; row++) {
for (let col = 0; col < grid[row].length; col++) {
total += grid[row][col];
}
}
console.log(`Sum: ${total}`); // Sum: 450Using for...of for Cleaner 2D Iteration
When you do not need the index:
const data = [
["Alice", "Bob", "Carol"],
["Dave", "Eve", "Frank"],
];
for (const row of data) {
for (const name of row) {
console.log(name);
}
}
// Alice, Bob, Carol, Dave, Eve, FrankMultiplication Table
A classic nested loop example that builds a formatted table:
const size = 10;
// Print header row
let header = " ";
for (let col = 1; col <= size; col++) {
header += String(col).padStart(5);
}
console.log(header);
console.log("-".repeat(header.length));
// Print table rows
for (let row = 1; row <= size; row++) {
let line = String(row).padStart(3) + " ";
for (let col = 1; col <= size; col++) {
line += String(row * col).padStart(5);
}
console.log(line);
}Output:
1 2 3 4 5 6 7 8 9 10
----------------------------------------------------
1 1 2 3 4 5 6 7 8 9 10
2 2 4 6 8 10 12 14 16 18 20
3 3 6 9 12 15 18 21 24 27 30
...
Finding Pairs and Combinations
All Unique Pairs
const items = ["A", "B", "C", "D"];
for (let i = 0; i < items.length; i++) {
for (let j = i + 1; j < items.length; j++) {
console.log(`${items[i]} - ${items[j]}`);
}
}
// A - B, A - C, A - D, B - C, B - D, C - DStarting the inner counter at i + 1 avoids duplicate pairs. Without this, you would get both "A - B" and "B - A".
Finding Two Numbers That Sum to a Target
function twoSum(numbers, target) {
for (let i = 0; i < numbers.length; i++) {
for (let j = i + 1; j < numbers.length; j++) {
if (numbers[i] + numbers[j] === target) {
return [i, j];
}
}
}
return null;
}
console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]
console.log(twoSum([3, 4, 5, 6], 11)); // [2, 3]Cartesian Product
const colors = ["red", "blue"];
const sizes = ["S", "M", "L"];
const combinations = [];
for (const color of colors) {
for (const size of sizes) {
combinations.push({ color, size });
}
}
console.log(combinations);
// [{ color: "red", size: "S" }, { color: "red", size: "M" },
// { color: "red", size: "L" }, { color: "blue", size: "S" },
// { color: "blue", size: "M" }, { color: "blue", size: "L" }]Pattern Printing
Right Triangle
const rows = 5;
for (let i = 1; i <= rows; i++) {
let line = "";
for (let j = 0; j < i; j++) {
line += "* ";
}
console.log(line);
}
// *
// * *
// * * *
// * * * *
// * * * * *Inverted Triangle
const rows = 5;
for (let i = rows; i >= 1; i--) {
let line = "";
for (let j = 0; j < i; j++) {
line += "* ";
}
console.log(line);
}
// * * * * *
// * * * *
// * * *
// * *
// *Number Pyramid
const rows = 5;
for (let i = 1; i <= rows; i++) {
let spaces = " ".repeat(rows - i);
let numbers = "";
for (let j = 1; j <= i; j++) {
numbers += j + " ";
}
console.log(spaces + numbers);
}
// 1
// 1 2
// 1 2 3
// 1 2 3 4
// 1 2 3 4 5Break and Continue in Nested Loops
Breaking the Inner Loop Only
const classrooms = [
{ room: "101", students: ["Alice", "Bob", "Carol"] },
{ room: "102", students: ["Dave", "Eve", "Frank"] },
{ room: "103", students: ["Grace", "Hank", "Ivy"] },
];
for (const classroom of classrooms) {
let found = false;
for (const student of classroom.students) {
if (student === "Eve") {
console.log(`Found Eve in room ${classroom.room}`);
found = true;
break; // exits inner loop only
}
}
if (found) break; // exits outer loop too
}Labeled Break for Cleaner Nested Exit
search:
for (const classroom of classrooms) {
for (const student of classroom.students) {
if (student === "Eve") {
console.log(`Found Eve in room ${classroom.room}`);
break search; // exits both loops
}
}
}Continue in Nested Loops
const data = [
[1, -1, 3],
[4, 5, -2],
[-3, 8, 9],
];
const positives = [];
for (const row of data) {
for (const value of row) {
if (value < 0) continue; // skip negatives, inner loop continues
positives.push(value);
}
}
console.log(positives); // [1, 3, 4, 5, 8, 9]Three-Level Nesting
Sometimes you need three levels. Keep the nesting readable with meaningful variable names:
const school = {
departments: [
{
name: "Science",
classes: [
{ name: "Physics", students: ["Alice", "Bob"] },
{ name: "Chemistry", students: ["Carol", "Dave"] },
],
},
{
name: "Arts",
classes: [
{ name: "Music", students: ["Eve"] },
{ name: "Drama", students: ["Frank", "Grace"] },
],
},
],
};
for (const dept of school.departments) {
for (const cls of dept.classes) {
for (const student of cls.students) {
console.log(`${dept.name} > ${cls.name} > ${student}`);
}
}
}Limit Nesting Depth
Three levels of nesting is the practical maximum for readability. Beyond three, refactor the inner logic into a separate function. A function call inside a loop is much easier to understand than four or five levels of indentation.
Performance Implications
Nested loops multiply iteration counts. This matters for large datasets:
| Outer | Inner | Total iterations | Time at 1M ops/sec |
|---|---|---|---|
| 10 | 10 | 100 | Instant |
| 100 | 100 | 10,000 | Instant |
| 1,000 | 1,000 | 1,000,000 | ~1 second |
| 10,000 | 10,000 | 100,000,000 | ~100 seconds |
// O(n^2): quadratic time
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length; j++) {
// runs arr.length * arr.length times
}
}
// Often reducible to O(n) with a Set or Map
const seen = new Set();
for (const item of arr) {
if (seen.has(item)) {
console.log("Duplicate found:", item);
}
seen.add(item);
}When to Avoid Nested Loops
// SLOW: O(n^2) duplicate check
function hasDuplicates(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) return true;
}
}
return false;
}
// FAST: O(n) with Set
function hasDuplicates(arr) {
return new Set(arr).size !== arr.length;
}Flattening Nested Arrays
// Manual flatten with nested loops
const nested = [[1, 2], [3, 4, 5], [6]];
const flat = [];
for (const subArray of nested) {
for (const item of subArray) {
flat.push(item);
}
}
console.log(flat); // [1, 2, 3, 4, 5, 6]
// Built-in alternative
console.log(nested.flat()); // [1, 2, 3, 4, 5, 6]Matrix Operations
Transpose a Matrix
function transpose(matrix) {
const rows = matrix.length;
const cols = matrix[0].length;
const result = [];
for (let col = 0; col < cols; col++) {
const newRow = [];
for (let row = 0; row < rows; row++) {
newRow.push(matrix[row][col]);
}
result.push(newRow);
}
return result;
}
const original = [
[1, 2, 3],
[4, 5, 6],
];
console.log(transpose(original));
// [[1, 4], [2, 5], [3, 6]]Best Practices
Use meaningful counter names. Replace i, j, k with row, col, layer or dept, team, member. Meaningful names make it immediately clear which counter belongs to which loop.
Limit nesting to three levels. Beyond three, extract inner loops into named functions. A function call is easier to understand than deep indentation.
Consider data structure alternatives. When nested loops exist to search (like finding duplicates or matching pairs), a Set, Map, or pre-sorted array often reduces complexity from O(n^2) to O(n) or O(n log n).
Break early when possible. If you are searching for a single result, use break or return as soon as you find it. Processing the remaining iterations wastes time on large datasets.
Watch the multiplication. Before writing nested loops over large collections, estimate the total iteration count. If it exceeds a million, profile the code and consider optimization.
Rune AI
Key Insights
- Inner loop runs completely for each outer iteration: total iterations = outer count times inner count
- Use meaningful variable names:
row/colinstead ofi/jprevents counter confusion - Limit to three nesting levels: beyond three, extract inner logic into named functions
- Watch the math: nested loops multiply iteration counts; estimate before running on large data
- Consider alternatives: Sets, Maps, and
.flat()often replace nested loops with better performance
Frequently Asked Questions
How many levels of nested loops can I have?
Are nested loops always O(n^2)?
How do I break out of all nested loops at once?
Can I mix different loop types in nested loops?
When should I use flat() instead of nested loops?
Conclusion
Nested loops let you process multi-dimensional data by running an inner loop completely for each iteration of an outer loop. They are essential for grids, matrices, combinations, and pattern generation. The key discipline is managing the iteration count: nested loops multiply, so a 1,000-item outer loop with a 1,000-item inner loop runs one million iterations. Use meaningful counter names, limit nesting to three levels, break early when searching, and consider data structure alternatives like Sets and Maps when nested loops exist only to compare elements.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.