Control Flow

Nested Loops

A nested loop is a loop (for, while, or loop) placed inside the body of another outer loop. For every single iteration of the outer loop, the inner loop runs from start to finish.

Execution Order and Multiplicative Iteration Count

When an outer loop runs $M$ times and an inner loop runs $N$ times for each outer pass, the body of the inner loop executes $M \times N$ times in total:

Outer Loop (row = 0..2, runs 2 times)
  │
  ├── Pass 1 (row = 0):
  │     └── Inner Loop (col = 0..3, runs 3 times): (0,0) -> (0,1) -> (0,2)
  │
  └── Pass 2 (row = 1):
        └── Inner Loop (col = 0..3, runs 3 times): (1,0) -> (1,1) -> (1,2)
                                                   (Total inner executions = 2 × 3 = 6)
fn main() {
    for row in 0..2 {
        for col in 0..3 {
            print!("({row}, {col}) ");
        }
        println!(); // Newline at the end of each outer row
    }
}
(0, 0) (0, 1) (0, 2) 
(1, 0) (1, 1) (1, 2) 

Traversing 2D Grids and Matrices

Nested for loops are the natural way to inspect or modify two-dimensional arrays ([[T; COLS]; ROWS]) and slices of slices. Iterating directly over references (for row in &matrix and for &cell in row) avoids manual index bounds checks:

fn main() {
    let matrix: [[i32; 3]; 3] = [
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9],
    ];

    let mut diagonal_sum = 0;

    for (r, row) in matrix.iter().enumerate() {
        for (c, &value) in row.iter().enumerate() {
            if r == c {
                diagonal_sum += value;
            }
        }
    }

    println!("Main diagonal sum: {diagonal_sum}");
}
Main diagonal sum: 15

Triangular Loops (Inner Bound Depends on Outer Variable)

In many algorithms—such as checking all unique pairs (i, j) in a slice without comparing an element to itself or checking (j, i) twice—the inner loop's range starts at i + 1:

fn main() {
    let items = [10, 20, 30, 40];

    println!("All unique pairs (i < j):");
    for i in 0..items.len() {
        for j in (i + 1)..items.len() {
            println!("  pair ({}, {}) -> sum = {}", items[i], items[j], items[i] + items[j]);
        }
    }
}
All unique pairs (i < j):
  pair (10, 20) -> sum = 30
  pair (10, 30) -> sum = 40
  pair (10, 40) -> sum = 50
  pair (20, 30) -> sum = 50
  pair (20, 40) -> sum = 60
  pair (30, 40) -> sum = 70

For $N$ elements, a triangular nested loop executes $\frac{N(N - 1)}{2}$ times (6 pairs for 4 elements) instead of $N^2$ (16).

Controlling Outer vs Inner Loops (break, continue, and 'labels)

Inside nested loops, unlabeled break and continue always target the innermost loop, while labeled break 'outer and continue 'outer target the named outer loop:

Statement Inside Inner Loop Effect on Inner Loop Effect on Outer Loop
continue; Advances to next inner iteration Continues current outer iteration
break; Terminates inner loop Continues rest of current outer iteration
continue 'outer; Terminates inner loop immediately Advances directly to next outer iteration
break 'outer; Terminates inner loop immediately Terminates outer loop completely
return expr; Terminates inner loop immediately Exits the entire enclosing function
fn first_duplicate(numbers: &[i32]) -> Option<i32> {
    let mut found: Option<i32> = None;

    'outer: for i in 0..numbers.len() {
        for j in (i + 1)..numbers.len() {
            if numbers[i] == numbers[j] {
                found = Some(numbers[i]);
                break 'outer; // Stop both loops as soon as the first duplicate is found
            }
        }
    }

    found
}

fn main() {
    println!("First duplicate: {:?}", first_duplicate(&[4, 9, 2, 9, 4]));
}
First duplicate: Some(9)

Variable Scoping and Reset Pitfalls in Nested Loops

When nesting while or loop constructs that use manual loop counters (let mut col = 0;), always declare or reset the inner loop counter inside the outer loop (just before the inner loop starts).

If you declare let mut col = 0; outside the outer loop and forget to reset col = 0; on each pass of the outer loop, the inner loop will run on the first outer iteration (row = 0, advancing col to 3) and then execute zero times on all subsequent outer iterations because col < 3 is already false!

fn main() {
    let mut row = 0;

    while row < 2 {
        // Declared INSIDE the outer loop so `col` resets to `0` on every row!
        let mut col = 0;

        while col < 3 {
            print!("[{row},{col}] ");
            col += 1;
        }
        println!();
        row += 1;
    }
}
[0,0] [0,1] [0,2] 
[1,0] [1,1] [1,2]