Skip to content
Home Python Nested Loops in Python

Nested Loops in Python

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Control Flow → Topic 5 of 7
How nested loops work in Python — for loops inside for loops, break and continue in nested loops, loop labels workaround, and when nested loops hurt performance.
🧑‍💻 Beginner-friendly — no prior Python experience needed
In this tutorial, you'll learn
How nested loops work in Python — for loops inside for loops, break and continue in nested loops, loop labels workaround, and when nested loops hurt performance.
  • Nested loops multiply iterations: outer × inner = total iterations. Always calculate this before writing the loop. 10,000 × 10,000 = 100 million iterations — seconds to minutes in Python.
  • break and continue only affect the innermost loop. To exit all loops, wrap the loops in a function and use return, or use a flag variable checked at each level.
  • Use enumerate() in nested loops when you need both index and value. It's cleaner than manual counters.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer

A nested loop is a loop inside another loop. The inner loop completes all its iterations for every single iteration of the outer loop. If the outer loop runs 3 times and the inner loop runs 4 times, you get 3 × 4 = 12 total iterations. That multiplication is also why nested loops get expensive fast — two loops over 10,000 items each means 100 million iterations.

Basic Nested Loop — How Iterations Multiply

The outer loop controls rows, the inner loop controls columns. Every time the outer loop ticks once, the inner loop runs to completion. This multiplication of iterations is the fundamental concept.

For i in range(3) runs 3 times. For j in range(4) runs 4 times. Nested: 3 × 4 = 12 total iterations. This scales fast: range(100) × range(100) = 10,000 iterations. range(10000) × range(10000) = 100,000,000 iterations. That last one will take minutes or hours depending on what's inside the loop.

Always think in terms of total iterations: outer_count × inner_count. If that product is more than about 10 million, you probably need a different approach.

io/thecodeforge/python/loops/basic_nested_loop.py · PYTHON
12345678910111213141516171819
# io.thecodeforge: Basic Nested Loop — Multiplication Table
total_iterations = 0

for i in range(1, 4):
    for j in range(1, 4):
        print(f'{i} x {j} = {i * j}', end='   ')
        total_iterations += 1
    print()

print(f'\nTotal iterations: {total_iterations}')
print(f'Formula: 3 outer x 3 inner = {3*3}')

print()

# Scaling warning — show how fast it grows
print('=== Iteration Scaling ===')
for n in [10, 100, 1000, 10000]:
    total = n * n
    print(f'range({n:>5}) x range({n:>5}) = {total:>12,} iterations')
▶ Output
1 x 1 = 1 1 x 2 = 2 1 x 3 = 3
2 x 1 = 2 2 x 2 = 4 2 x 3 = 6
3 x 1 = 3 3 x 2 = 6 3 x 3 = 9

Total iterations: 9
Formula: 3 outer x 3 inner = 9

=== Iteration Scaling ===
range( 10) x range( 10) = 100 iterations
range( 100) x range( 100) = 10,000 iterations
range( 1000) x range( 1000) = 1,000,000 iterations
range(10000) x range(10000) = 100,000,000 iterations
💡The 10-Million Rule:
If outer_count × inner_count exceeds 10 million, pause and ask: can I use a set lookup, a dictionary, a built-in function, or a library like NumPy instead? At 100 million iterations, even a simple addition inside the loop takes minutes in pure Python. At 1 billion, you're waiting hours.

Iterating Over a 2D List — The Most Natural Use Case

The most natural use of nested loops is walking through a matrix or a list of lists. The outer loop picks the row, the inner loop picks the column. This pattern shows up everywhere: image processing (pixel grids), spreadsheet data (rows and columns), game boards (chess, tic-tac-toe), and database result sets.

Use enumerate() when you need both the index and the value. Using a manual counter variable instead of enumerate() works but is less Pythonic and more error-prone.

io/thecodeforge/python/loops/matrix_iteration.py · PYTHON
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
# io.thecodeforge: 2D List Iteration Patterns
# Real-world patterns for processing matrices and grids

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

# Pattern 1: Sum all elements
total = 0
for row in matrix:
    for value in row:
        total += value
print(f'Sum of all elements: {total}')  # 45

print()

# Pattern 2: Find position of a value (using enumerate for indices)
target = 5
for row_idx, row in enumerate(matrix):
    for col_idx, value in enumerate(row):
        if value == target:
            print(f'Found {target} at row={row_idx}, col={col_idx}')

print()

# Pattern 3: Transpose a matrix (swap rows and columns)
rows = len(matrix)
cols = len(matrix[0])
transposed = []
for col in range(cols):
    new_row = []
    for row in range(rows):
        new_row.append(matrix[row][col])
    transposed.append(new_row)

print('Original:')
for row in matrix:
    print(f'  {row}')
print('Transposed:')
for row in transposed:
    print(f'  {row}')

print()

# Pattern 4: Process CSV-like data (rows = records, columns = fields)
students = [
    ['Alice', 92, 88, 95],
    ['Bob',   78, 85, 80],
    ['Carol', 95, 91, 97],
]

print('Student Averages:')
for student in students:
    name = student[0]
    scores = student[1:]
    avg = sum(scores) / len(scores)
    print(f'  {name}: {avg:.1f}')
▶ Output
Sum of all elements: 45

Found 5 at row=1, col=1

Original:
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
Transposed:
[1, 4, 7]
[2, 5, 8]
[3, 6, 9]

Student Averages:
Alice: 91.7
Bob: 81.0
Carol: 94.3
🔥enumerate() Is Your Best Friend in Nested Loops:
Whenever you need both the index and the value in a loop, use enumerate(). Writing for i in range(len(list)) followed by list[i] works but is less readable and more error-prone. enumerate() gives you the index and value in one clean line: for idx, value in enumerate(list). In nested loops this saves even more visual clutter.

Mixed Loop Nesting — for, while, and Combinations

Most tutorials only show for-inside-for. But production code uses all combinations: for-inside-while, while-inside-for, and while-inside-while. Each combination has a specific use case.

for inside while: Use when the outer condition is dynamic (like reading from a stream) but the inner iteration is fixed (like processing each field in a record).

while inside for: Use when iterating over a collection but each item requires a variable number of steps (like retrying an API call until it succeeds).

while inside while: Rare but useful for multi-stage processing where both stages have dynamic termination conditions.

The key with mixed nesting: make sure every loop has a guaranteed exit condition. A while loop inside a for loop where the while condition never becomes false is an infinite loop that will freeze your program.

io/thecodeforge/python/loops/mixed_loop_nesting.py · PYTHON
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
# io.thecodeforge: Mixed Loop Nesting Patterns
# Real-world combinations of for and while loops

# Pattern 1: for inside while — processing a stream of records
# Outer: keep reading until stream is empty (dynamic)
# Inner: process fixed fields in each record (fixed)
print('=== Pattern 1: for inside while ===')
records = [
    {'name': 'Alice', 'scores': [90, 85, 92]},
    {'name': 'Bob',   'scores': [78, 82, 80]},
]

record_index = 0
while record_index < len(records):
    record = records[record_index]
    print(f"Processing {record['name']}:")
    for score in record['scores']:
        print(f'  Score: {score}')
    record_index += 1

print()

# Pattern 2: while inside for — retry logic per item
# Outer: iterate over API endpoints (fixed collection)
# Inner: retry until success or max attempts (dynamic)
print('=== Pattern 2: while inside for (retry pattern) ===')
endpoints = ['users', 'orders', 'products']

for endpoint in endpoints:
    attempt = 0
    max_attempts = 3
    success = False
    while attempt < max_attempts and not success:
        attempt += 1
        # Simulate: succeed on attempt 2 for 'orders', first attempt for others
        if endpoint == 'orders' and attempt < 2:
            print(f'  {endpoint}: attempt {attempt} failed, retrying...')
        else:
            success = True
            print(f'  {endpoint}: success on attempt {attempt}')

print()

# Pattern 3: Nested for with zip — parallel iteration
print('=== Pattern 3: Parallel iteration with zip ===')
products = ['Widget A', 'Widget B', 'Widget C']
prices = [29.99, 49.99, 19.99]
quantities = [3, 1, 5]

for product, price, qty in zip(products, prices, quantities):
    total = price * qty
    print(f'{product}: {qty} x ${price:.2f} = ${total:.2f}')
▶ Output
=== Pattern 1: for inside while ===
Processing Alice:
Score: 90
Score: 85
Score: 92
Processing Bob:
Score: 78
Score: 82
Score: 80

=== Pattern 2: while inside for (retry pattern) ===
users: success on attempt 1
orders: attempt 1 failed, retrying...
orders: success on attempt 2
products: success on attempt 1

=== Pattern 3: Parallel iteration with zip ===
Widget A: 3 x $29.99 = $89.97
Widget B: 1 x $49.99 = $49.99
Widget C: 5 x $19.99 = $99.95
⚠ Guarantee Every While Loop Exits:
A while loop inside a for loop where the while condition never becomes false is an infinite loop. Always ensure there's a counter, a timeout, or a success condition that will eventually break the while. In production, an infinite loop consumes 100% CPU and can take down your server. I've seen a cron job with a nested while loop run for 6 hours at 100% CPU before anyone noticed because the while condition depended on an API that had gone down.

break and continue in Nested Loops — The Scope Trap

Here is where most beginners hit a wall: break only exits the innermost loop it is in, not all loops. continue only skips to the next iteration of the innermost loop. Neither affects outer loops.

This is the #1 source of 'my code doesn't stop when I expect it to' bugs with nested loops. If you break inside the inner loop, the outer loop keeps running.

To exit ALL nested loops, you have three options: 1. Flag variable — set a flag in the inner loop, check it in the outer loop 2. Function + return — wrap the loops in a function and use return to exit everything 3. Exception — raise and catch an exception (hacky, not recommended)

The function approach is the cleanest and most Pythonic. The flag approach works but adds clutter.

io/thecodeforge/python/loops/break_continue_nested.py · PYTHON
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
# io.thecodeforge: break and continue in Nested Loops
# Shows scope limitations and workarounds

# DEMO 1: break only exits the inner loop
print('=== break exits inner loop only ===')
for i in range(3):
    for j in range(3):
        if j == 1:
            break  # exits inner loop only — outer loop keeps going
        print(f'i={i}, j={j}')
print('Outer loop continued after break\n')
# Output: i=0,j=0 / i=1,j=0 / i=2,j=0
# j never reaches 1 or 2, but all 3 outer iterations complete

# DEMO 2: continue skips current inner iteration only
print('=== continue skips inner iteration only ===')
for i in range(3):
    for j in range(3):
        if j == 1:
            continue  # skips j=1, continues with j=2
        print(f'i={i}, j={j}')
print()
# Output: j=0 and j=2 for every i — j=1 is skipped each time

# DEMO 3: Flag variable — exit both loops
print('=== Flag variable to exit both loops ===')
found = False
for i in range(5):
    for j in range(5):
        if i * j > 6:
            found = True
            break  # exits inner loop
    if found:
        break  # exits outer loop too
print(f'Stopped at i={i}, j={j} (product={i*j})\n')

# DEMO 4: Function + return — cleanest approach
print('=== Function + return (recommended) ===')
def find_first_value_above(matrix, threshold):
    """Search a 2D matrix and return the first value above threshold."""
    for i, row in enumerate(matrix):
        for j, val in enumerate(row):
            if val > threshold:
                return i, j, val  # exits ALL loops instantly
    return None  # nothing found

matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]
result = find_first_value_above(matrix, 5)
print(f'First value > 5: {result}')
▶ Output
=== break exits inner loop only ===
i=0, j=0
i=1, j=0
i=2, j=0
Outer loop continued after break

=== continue skips inner iteration only ===
i=0, j=0
i=0, j=2
i=1, j=0
i=1, j=2
i=2, j=0
i=2, j=2

=== Flag variable to exit both loops ===
Stopped at i=2, j=4 (product=8)

=== Function + return (recommended) ===
First value > 5: (2, 0, 7)
⚠ break Only Exits the Innermost Loop:
This is the single most common bug with nested loops. If you expect break to exit everything, you'll be confused when the outer loop keeps running. Use the function+return pattern when you need to exit all loops. It's the cleanest, most readable, and most reliable solution. I've seen production bugs where a break was intended to exit a validation routine but only exited the inner loop, causing the same invalid record to be processed multiple times.

Pattern Printing — The Classic Nested Loop Exercise

If you've ever taken a programming course, you've printed triangles of stars with nested loops. It looks like a toy exercise, but it teaches something genuinely important: the outer loop controls the number of rows, and the inner loop controls what happens in each row.

The key insight: the inner loop's range often depends on the outer loop's current value. In a right triangle of stars, row 1 prints 1 star, row 2 prints 2 stars, row 5 prints 5 stars. The inner loop's range is range(1, i+1) — it changes every iteration of the outer loop.

This pattern of 'inner loop range depends on outer loop variable' shows up in real code too: comparing every pair of items, building triangular matrices, generating combinations.

io/thecodeforge/python/loops/pattern_printing.py · PYTHON
123456789101112131415161718192021222324252627282930313233343536373839404142
# io.thecodeforge: Pattern Printing with Nested Loops
# Classic exercises that teach inner-loop-range-dependency

rows = 5

# Pattern 1: Right triangle
print('=== Right Triangle ===')
for i in range(1, rows + 1):
    for j in range(i):
        print('*', end='')
    print()

print()

# Pattern 2: Inverted right triangle
print('=== Inverted Right Triangle ===')
for i in range(rows, 0, -1):
    for j in range(i):
        print('*', end='')
    print()

print()

# Pattern 3: Pyramid (centered)
print('=== Pyramid ===')
for i in range(1, rows + 1):
    # Print leading spaces
    for space in range(rows - i):
        print(' ', end='')
    # Print stars
    for star in range(2 * i - 1):
        print('*', end='')
    print()

print()

# Pattern 4: Number triangle
print('=== Number Triangle ===')
for i in range(1, rows + 1):
    for j in range(1, i + 1):
        print(j, end=' ')
    print()
▶ Output
=== Right Triangle ===
*
**
***
****
*****

=== Inverted Right Triangle ===
*****
****
***
**
*

=== Pyramid ===
*
***
*****
*******
*********

=== Number Triangle ===
1
1 2
1 2 3
1 2 3 4
1 2 3 4 5
🔥The Real Lesson: Inner Range Depends on Outer Variable:
Pattern printing teaches a concept you'll use everywhere: the inner loop's range can depend on the outer loop's current value. range(i) inside for i in range(n) means the inner loop grows each iteration. This same pattern appears in pair comparison (for i in range(n): for j in range(i+1, n):), triangular matrix construction, and combination generation.

Flattening and Comprehensions — Pythonic Nested Loops

A common task — turning a list of lists into a flat list. You can do it with explicit nested loops, but Python offers cleaner alternatives.

List comprehensions can express nested loops in one line. The syntax reads left-to-right like the loop version: [item for sublist in nested for item in sublist] means 'for each sublist, for each item in that sublist, keep the item.' The order matches the nested for loop — outer first, inner second.

itertools.chain.from_iterable is the fastest option for flattening because it's implemented in C and uses lazy evaluation — no intermediate lists are built.

Use the explicit loop when the logic is complex. Use the comprehension when it's a simple transform. Use itertools when performance matters on large datasets.

io/thecodeforge/python/loops/flattening_comprehensions.py · PYTHON
1234567891011121314151617181920212223242526272829303132333435
# io.thecodeforge: Flattening and Comprehension Patterns

nested = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]

# Method 1: Explicit nested loop
flat_loop = []
for sublist in nested:
    for item in sublist:
        flat_loop.append(item)
print(f'Explicit loop:   {flat_loop}')

# Method 2: List comprehension — same result, one line
flat_comp = [item for sublist in nested for item in sublist]
print(f'Comprehension:   {flat_comp}')

# Method 3: itertools.chain — fastest for large lists
import itertools
flat_chain = list(itertools.chain.from_iterable(nested))
print(f'itertools.chain: {flat_chain}')

print()

# Nested comprehension with filtering
# Get all even numbers from a 2D list
evens = [val for row in nested for val in row if val % 2 == 0]
print(f'Even numbers: {evens}')

print()

# Cartesian product — every combination of two lists
# This is what nested loops really do under the hood
letters = ['a', 'b', 'c']
numbers = [1, 2, 3]
pairs = [(l, n) for l in letters for n in numbers]
print(f'Cartesian product: {pairs}')
▶ Output
Explicit loop: [1, 2, 3, 4, 5, 6, 7, 8, 9]
Comprehension: [1, 2, 3, 4, 5, 6, 7, 8, 9]
itertools.chain: [1, 2, 3, 4, 5, 6, 7, 8, 9]

Even numbers: [2, 4, 6, 8]

Cartesian product: [('a', 1), ('a', 2), ('a', 3), ('b', 1), ('b', 2), ('b', 3), ('c', 1), ('c', 2), ('c', 3)]
💡Comprehension Order Matches Loop Order:
The comprehension [x for a in list1 for b in list2] matches the loop: outer first (a in list1), inner second (b in list2). Beginners often reverse this order. Think of it as reading left to right: the first for is the outer loop, the second for is the inner loop. If you need three levels of nesting, add a third for — but at that point, an explicit loop is usually more readable.

Performance — When Nested Loops Become a Production Problem

Two nested loops over n items = O(n²). That's manageable for n=100 but slow at n=10,000 and unusable at n=1,000,000. Three nested loops = O(n³). Each additional nesting level multiplies the cost.

The most common production performance fix: replace an inner loop with a set or dictionary lookup. If you're looping through list A and for each item looping through list B to check if it exists, you can convert list B to a set and do if item in set_B — turning O(n×m) into O(n+m).

I once debugged a duplicate detection system that compared every record against every other record using nested loops. With 50,000 records, that's 2.5 billion comparisons. The system ran for 4 hours on every batch. The fix: sort the records first, then compare only adjacent items. Same result, O(n log n) instead of O(n²). Runtime dropped from 4 hours to 12 seconds.

The takeaway: before you write a nested loop, calculate total iterations. If it's over 10 million, you need a better algorithm.

io/thecodeforge/python/loops/performance_optimization.py · PYTHON
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
# io.thecodeforge: Nested Loop Performance Patterns
# Real-world optimization examples

import time
import random

# BAD: O(n²) duplicate check
print('=== BAD: O(n²) Duplicate Detection ===')
items = list(range(10000))
# Add a few duplicates
items.append(5000)
items.append(8000)

def find_duplicates_slow(items):
    duplicates = []
    n = len(items)
    for i in range(n):
        for j in range(i + 1, n):
            if items[i] == items[j]:
                duplicates.append(items[i])
    return duplicates

# GOOD: O(n) using a set
print('=== GOOD: O(n) Set-Based Duplicate Detection ===')
def find_duplicates_fast(items):
    seen = set()
    duplicates = set()
    for item in items:
        if item in seen:
            duplicates.add(item)
        else:
            seen.add(item)
    return list(duplicates)

# BAD: Nested loop lookup
def find_matches_slow(list_a, list_b):
    matches = []
    for a in list_a:
        for b in list_b:
            if a == b:
                matches.append(a)
    return matches

# GOOD: Set-based lookup
def find_matches_fast(list_a, list_b):
    set_b = set(list_b)
    return [a for a in list_a if a in set_b]

# PAIR COMPARISON: j = i+1 pattern
print('=== Pair Comparison: Avoid Double-Counting ===')
items = ['A', 'B', 'C', 'D']

# BAD: compares each pair twice (A-B and B-A)
print('BAD: O(n²) with double counting:')
for i in range(len(items)):
    for j in range(len(items)):
        if i != j:
            print(f'  Compare {items[i]} vs {items[j]}')

# GOOD: each pair once
print('\nGOOD: O(n²/2) with single counting:')
for i in range(len(items)):
    for j in range(i + 1, len(items)):
        print(f'  Compare {items[i]} vs {items[j]}')
▶ Output
=== BAD: O(n²) Duplicate Detection ===
=== GOOD: O(n) Set-Based Duplicate Detection ===

=== Pair Comparison: Avoid Double-Counting ===
BAD: O(n²) with double counting:
Compare A vs B
Compare A vs C
Compare A vs D
Compare B vs A
Compare B vs C
Compare B vs D
Compare C vs A
Compare C vs B
Compare C vs D
Compare D vs A
Compare D vs B
Compare D vs C

GOOD: O(n²/2) with single counting:
Compare A vs B
Compare A vs C
Compare A vs D
Compare B vs C
Compare B vs D
Compare C vs D
⚠ The Performance Trap:
Two nested loops over 10,000 items is 100 million iterations. Python does about 10-50 million simple operations per second on modern hardware. At 100 million, you're already in seconds to minutes. At 1 billion, you're in hours. Always calculate total iterations before writing a nested loop. If the product exceeds 10 million, you need a better algorithm. I've seen 4-hour ETL jobs become 12-second jobs by replacing O(n²) with O(n log n).

Real-World Patterns — Where Nested Loops Actually Live

Theory is fine, but here are the patterns where nested loops appear in real code every day:

1. CSV/Excel Processing: For each row, for each column, validate/transform data. This is exactly the 200k row × 40 column pipeline that took 11 hours before optimization.

2. Duplicate Detection: For each record, check against every other record to find duplicates. This is the classic O(n²) trap. The fix is hashing or sorting.

3. Cartesian Product: Generate all combinations of options. Product catalog: sizes × colors × styles = all SKUs. This is intentional O(n×m×p) and is fine when the product dimensions are small.

4. Adjacent Comparisons: For i in range(n-1): compare item[i] with item[i+1]. This is O(n) not O(n²). Often used in time-series analysis to detect spikes.

5. Matrix Operations: Adding two matrices, finding the maximum, transposing. These are naturally O(rows × cols) and unavoidable.

io/thecodeforge/python/loops/real_world_patterns.py · PYTHON
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
# io.thecodeforge: Real-World Nested Loop Patterns

print('=== Pattern 1: CSV Validation ===')
csv_data = [
    ['Alice', '25', 'alice@example.com'],
    ['Bob', 'invalid', 'bob@example.com'],
    ['Carol', '30', 'not-an-email'],
]

for row_idx, row in enumerate(csv_data):
    name, age_str, email = row
    errors = []
    try:
        age = int(age_str)
        if age < 0 or age > 120:
            errors.append('age out of range')
    except ValueError:
        errors.append('age not a number')
    if '@' not in email or '.' not in email:
        errors.append('invalid email format')
    if errors:
        print(f'Row {row_idx + 1}: {", ".join(errors)}')

print('\n=== Pattern 2: Duplicate Detection with Hashing ===')
# Instead of O(n²) nested loops, use a set
users = [
    {'id': 1, 'email': 'alice@example.com'},
    {'id': 2, 'email': 'bob@example.com'},
    {'id': 3, 'email': 'alice@example.com'},  # duplicate email
    {'id': 4, 'email': 'carol@example.com'},
]

seen_emails = {}
for user in users:
    email = user['email']
    if email in seen_emails:
        print(f'Duplicate email "{email}": user {seen_emails[email]} and user {user["id"]}')
    else:
        seen_emails[email] = user['id']

print('\n=== Pattern 3: Cartesian Product (SKU Generation) ===')
sizes = ['S', 'M', 'L']
colors = ['Red', 'Blue']
styles = ['Crew', 'V-Neck']

skus = []
for size in sizes:
    for color in colors:
        for style in styles:
            sku = f'{size}-{color}-{style}'
            skus.append(sku)
print(f'{len(skus)} SKUs generated:')
for sku in skus[:5]:  # show first 5 only
    print(f'  {sku}')
print('  ...')

print('\n=== Pattern 4: Adjacent Comparison (Time Series Spikes) ===')
metrics = [100, 102, 105, 200, 101, 98, 95]
for i in range(1, len(metrics)):
    prev = metrics[i - 1]
    curr = metrics[i]
    if curr > prev * 1.5:  # 50% spike
        print(f'Spike at position {i}: {prev} → {curr}')
▶ Output
=== Pattern 1: CSV Validation ===
Row 2: age not a number
Row 3: invalid email format

=== Pattern 2: Duplicate Detection with Hashing ===
Duplicate email "alice@example.com": user 1 and user 3

=== Pattern 3: Cartesian Product (SKU Generation) ===
12 SKUs generated:
S-Red-Crew
S-Red-V-Neck
S-Blue-Crew
S-Blue-V-Neck
M-Red-Crew
...

=== Pattern 4: Adjacent Comparison (Time Series Spikes) ===
Spike at position 3: 105 → 200
💡j = i + 1 — Avoid Comparing an Item to Itself:
When doing pair comparison, start the inner loop at range(i + 1, n) instead of range(n). This avoids comparing an item to itself and avoids comparing the same pair twice (A-B and B-A). It cuts your iterations nearly in half: n(n-1)/2 instead of n². This is the standard pattern for duplicate detection, similarity scoring, and conflict checking.
Featurefor loopwhile loopNested loop (any type)
When to useKnown iteration count or iterating over a collectionCondition-based — don't know how many iterations upfrontProcessing 2D data, pair comparison, cartesian products
TerminationAutomatically ends when sequence is exhaustedMust become false inside the loop bodyEach level must terminate independently
break behaviorExits the loop immediatelyExits the loop immediatelyOnly exits the innermost loop — outer loops continue
continue behaviorSkips to next iterationSkips to next iterationOnly skips the current inner iteration — outer loop unaffected
Common mistakeOff-by-one in range()Infinite loop if condition never becomes falseConfusing outer/inner variables, forgetting break only affects inner
Performance concernO(n) — usually fineO(n) — usually fineO(n²) or worse — always calculate total iterations
Pythonic alternativeList comprehension, enumerate, zipRarely has a cleaner alternativeitertools.product, set lookups, dictionary indexing

🎯 Key Takeaways

  • Nested loops multiply iterations: outer × inner = total iterations. Always calculate this before writing the loop. 10,000 × 10,000 = 100 million iterations — seconds to minutes in Python.
  • break and continue only affect the innermost loop. To exit all loops, wrap the loops in a function and use return, or use a flag variable checked at each level.
  • Use enumerate() in nested loops when you need both index and value. It's cleaner than manual counters.
  • When checking membership, replace nested loops with set or dictionary lookups: O(n²) → O(n). This is the single most impactful performance optimisation for nested loops.
  • For pair comparison, use for i in range(n): for j in range(i+1, n): to avoid double-counting and self-comparison. This cuts iterations roughly in half.
  • List comprehensions can express nested loops in one line: [item for sublist in nested for item in sublist]. The order matches the nested loop order — outer first, inner second.
  • Three levels of nesting or more is a code smell. Consider whether you can use itertools.product or restructure your data.
  • Nested loops are unavoidable for matrix operations (rows × cols) and generating cartesian products. They become dangerous when used for lookups, searches, or comparisons that could use hashing.

⚠ Common Mistakes to Avoid

    Using the wrong variable in the inner loop — reusing the outer loop variable inside the inner loop (`for i in range(3): for i in range(4):`). This overwrites the outer variable and leads to off-by-one bugs. Use distinct names like `i` and `j`.

    i and j`.

    Expecting `break` to exit all nested loops. It only exits the innermost loop. The outer loop continues. Use the function+return pattern to exit everything cleanly.

    ng cleanly.

    Creating O(n²) performance unintentionally. Two nested loops over the same large list without realising you're doing `len(list)²` iterations. Always calculate total iterations before writing a nested loop.

    ested loop.

    Forgetting that `continue` only affects the innermost loop. If you `continue` in an inner loop, the outer loop keeps running.

    ps running.

    Not using `enumerate()` when you need indices. Writing manual index counters (`i = 0; for item in list: i += 1`) is error-prone and less readable.

    s readable.

    Using nested loops for lookup operations. If you're checking membership of one list in another, use a set. Nested loops for this are O(n²); a set is O(n).

    et is O(n).

    Nesting three or more levels deep without a clear reason. By the time you're three loops deep, you're probably doing something that could be replaced with itertools.product or a better data structure.

    structure.

Interview Questions on This Topic

  • QWhat happens when you use break inside an inner loop? Does it exit the outer loop too?
  • QHow would you find all duplicate emails in a list of user records without using O(n²) nested loops?
  • QWhat is the total number of iterations for for i in range(1000): for j in range(1000):? Is this acceptable performance?
  • QHow do you flatten a list of lists into a single flat list? Show three different ways.
  • QWhat is the difference between break and continue in nested loops?
  • QWhen would you use while inside for instead of for inside for?
  • QWhat is the performance difference between if item in list inside a loop versus if item in set?
  • QHow would you iterate over a 2D list to get both the row index and column index for each element?
  • QWhat is the 'flag variable' pattern for breaking out of multiple nested loops? Is there a better alternative?
  • QWhy does for i in range(n): for j in range(i+1, n): only compare each pair once instead of twice?

Frequently Asked Questions

How do I break out of multiple nested loops in Python?

Use the function+return pattern: wrap the nested loops in a function and call return when you want to exit everything. This is the cleanest, most Pythonic approach. Alternatively, use a flag variable that you check after each loop level. Avoid using exceptions for flow control — it's slower and less readable.

Why is my nested loop so slow?

Nested loops multiply iterations. If you have 10,000 items in the outer loop and 10,000 in the inner, that's 100 million iterations. Python can handle about 10-50 million simple operations per second on modern hardware. If your loop does complex work, it will be slower. The fix is usually replacing an inner loop with a set or dictionary lookup, turning O(n²) into O(n).

What's the difference between a nested loop and a double loop comprehension?

A nested loop writes the loops explicitly. A comprehension like [x for a in list1 for b in list2] does exactly the same thing — it's syntactic sugar. The comprehension is more concise and often faster because it's optimised internally. Use comprehensions for simple transforms; use explicit loops for complex logic, early exits, or side effects.

How do I iterate over a 2D list with indices?

Use enumerate on both levels: for i, row in enumerate(matrix): for j, val in enumerate(row): if val == target: return (i, j). This gives you both the row and column index. Don't use matrix.index() — it only searches the top level and won't find elements inside sublists.

When should I use while inside for instead of for inside for?

Use while inside for when the inner loop has a dynamic exit condition that depends on external factors — like retrying an API call until success, or reading from a stream until a delimiter. Use for inside for when both loops iterate over fixed collections or known ranges. Mixed nesting is common in retry patterns and stream processing.

How do I avoid O(n²) performance when comparing every pair?

If you need to compare every pair of items (like duplicate detection), you have two options. Option 1: use a hash-based approach (set or dictionary) to detect duplicates in O(n) instead of O(n²). Option 2: if you truly need to compare every pair, you can't avoid O(n²), but you can cut the count in half by using for i in range(n): for j in range(i+1, n): instead of nested ranges that compare each pair twice.

Can I use `continue` to skip the outer loop iteration from inside the inner loop?

No. continue only affects the innermost loop it's in. To skip the outer loop iteration from inside the inner loop, you need a flag: set skip_outer = True in the inner loop, then check if skip_outer: continue in the outer loop. Alternatively, wrap the inner loop in a function that returns a sentinel value indicating whether to skip the outer iteration.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← Previousbreak continue pass in PythonNext →match-case Statement in Python 3.10
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged