Python Nested Loops
Loop Satellite
Nested Loops
Loop inside a loop to handle matrices, cartesian products, or grouped data—but keep complexity under control.
Iterating a grid
A nested loop runs the inner loop to completion for every iteration of the outer loop. That matches the shape of two-dimensional data: outer loop per row, inner loop per value.
grid = [
[1, 2, 3],
[4, 5, 6],
]
for row in grid:
for value in row:
print(value, end=' ')
print()
# 1 2 3
# 4 5 6
The inner print(value, end=' ') keeps each row on one line; the bare print() after the inner loop starts the next row. When you need positions as well as values, index with range and len:
grid = [
[1, 2, 3],
[4, 5, 6],
]
for r in range(len(grid)):
for c in range(len(grid[r])):
print(f'({r},{c})={grid[r][c]}', end=' ')
print()
# (0,0)=1 (0,1)=2 (0,2)=3
# (1,0)=4 (1,1)=5 (1,2)=6
Looping over pairs
Nested loops also generate every combination of two independent sequences:
sizes = ['S', 'M']
colors = ['red', 'blue']
for size in sizes:
for color in colors:
print(size, color)
# S red
# S blue
# M red
# M blue
itertools.product produces the same combinations with a single, flat loop — handy when the nesting would otherwise get deep:
from itertools import product
sizes = ['S', 'M']
colors = ['red', 'blue']
for size, color in product(sizes, colors):
print(size, color)
# S red
# S blue
# M red
# M blue
Breaking out of nested loops
break only exits the loop it appears in. To stop both loops — say, on the first pair of numbers that sums to a target — you have three standard options.
Option 1: a flag variable. Explicit but noisy:
numbers = [3, 5, 7, 8]
found = False
for a in numbers:
for b in numbers:
if a + b == 10:
print(a, b) # -> 3 7
found = True
break
if found:
break
Option 2: wrap in a function and return. Usually the cleanest — return exits every loop at once, and the search gets a name:
def first_pair(numbers, target):
for a in numbers:
for b in numbers:
if a + b == target:
return a, b
return None
print(first_pair([3, 5, 7, 8], 10)) # -> (3, 7)
Option 3: the for/else pattern. A loop's else clause runs only when the loop finished without break, which lets the outer loop distinguish "inner loop broke" from "inner loop ran dry":
numbers = [3, 5, 7, 8]
for a in numbers:
for b in numbers:
if a + b == 10:
print('found', a, b) # -> found 3 7
break
else:
continue # inner loop finished normally — keep searching
break # inner loop hit break — stop the outer loop too
Cost grows multiplicatively
The inner body runs n * m times: for the 2×3 grid above that is 6 iterations — harmless. But scale both loops and the product explodes: comparing every user in a list of 1,000 against every one of 1,000 orders runs the body 1,000,000 times, and at 10,000 each it is 100,000,000. When a nested loop over large data feels slow, that multiplication is why — look for a way to replace the inner scan with a set or dict lookup, which cuts the work back to roughly n + m.
Refactoring
- Extract inner loops into helper functions — you gain a name and a
return-based early exit. - Use
itertools.productfor cartesian products instead of literal nesting. - Convert simple transform loops into comprehensions.
- Consider vectorized libraries (NumPy) for heavy numeric grid work.
Frequently Asked Questions
How do I break out of two loops at once?
Python has no labeled break. Wrap the loops in a function and return, use a flag variable checked by the outer loop, or use the for/else idiom. The function approach is usually the most readable because return unconditionally exits all loops.
def find(grid, target):
for row in grid:
for value in row:
if value == target:
return value
return None
What is the difference between zip() and a nested loop?
zip pairs items by position and stops at the shorter input, so two lists of 3 give 3 pairs. A nested loop (or itertools.product) combines every item with every other item, giving 9 pairs. Use zip for parallel iteration, nesting for all combinations.
names = ['Ada', 'Bob']
ages = [36, 41]
for name, age in zip(names, ages):
print(name, age)
# Ada 36
# Bob 41
Are nested loops bad practice?
No — they are the natural way to walk 2D data, and two levels are perfectly readable. They become a problem when both loops are large (the body runs n * m times) or when nesting passes three levels deep. In those cases replace the inner scan with a set/dict lookup or extract helper functions.