Technology  /  Python

🐍 Python 78 guides · updated 2026

From first variable to OOP, generators, and real projects — the language that runs everything from data pipelines to AI agents, taught the practical way.

Python Nested Loops: When to Use Them and When to Refactor Them Away

A nested loop is a loop inside another loop. For every iteration of the outer loop, the inner loop runs to completion. If the outer loop runs m times and the inner loop runs n times, the body of the inner loop executes m × n times. That multiplication is both the power and the danger of nested loops.

Basic Mechanics

for row in range(3):
for col in range(4):
print(f"({row},{col})", end=" ")
print() # newline after each row
# (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 inner loop completes all four iterations for each single iteration of the outer loop. The total number of print() calls is 3 × 4 = 12.

Common Use Cases

2D Data (Matrices)

matrix = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
]
# Sum all elements
total = 0
for row in matrix:
for value in row:
total += value
print(total) # 78
# Transpose: swap rows and columns
rows = len(matrix)
cols = len(matrix[0])
transposed = [[matrix[r][c] for r in range(rows)] for c in range(cols)]
print(transposed)
# [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

Generating All Combinations

sizes = ["S", "M", "L", "XL"]
colours = ["Red", "Blue", "Green"]
combinations = []
for size in sizes:
for colour in colours:
combinations.append(f"{colour} {size}")
print(len(combinations)) # 12
print(combinations[:4])
# ['Red S', 'Blue S', 'Green S', 'Red M']

This is equivalent to a Cartesian product — every size paired with every colour.

Comparing All Pairs

scores = [88, 92, 75, 88, 60, 92]
# Find all pairs of equal scores
duplicates = []
for i in range(len(scores)):
for j in range(i + 1, len(scores)): # j starts after i to avoid duplicates
if scores[i] == scores[j]:
duplicates.append((i, j, scores[i]))
print(duplicates)
# [(0, 3, 88), (1, 5, 92)]

Searching in a 2D Grid

grid = [
[0, 0, 1, 0],
[0, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 1],
]
targets = []
for row in range(len(grid)):
for col in range(len(grid[row])):
if grid[row][col] == 1:
targets.append((row, col))
print(targets) # [(0, 2), (2, 1), (3, 3)]

Breaking Out of Nested Loops

Python does not have a “break outer loop” keyword. The standard workarounds are:

Option 1: Use a flag variable

found = False
for i in range(5):
for j in range(5):
if i * j == 12:
found = True
result = (i, j)
break
if found:
break
print(result) # (3, 4)

Option 2: Extract into a function and return

def find_product(target, max_val):
for i in range(max_val + 1):
for j in range(max_val + 1):
if i * j == target:
return (i, j)
return None
print(find_product(12, 5)) # (2, 6) — first match

This is usually cleaner. Returning from a function immediately exits all loops.

Option 3: Use exceptions (in specific circumstances)

class Found(Exception):
pass
try:
for i in range(100):
for j in range(100):
if i ** 2 + j ** 2 == 625:
raise Found(i, j)
except Found as e:
print(f"Found: {e.args}") # Found: (7, 24)

This is unusual but occasionally the cleanest option when the target is buried deep in multiple nesting levels.

The O(n²) Problem

Nested loops over inputs of the same size produce quadratic time complexity. If each list has n items, the nested loop runs iterations.

import time
def find_common_naive(list1, list2):
"""O(n²) — check every pair"""
common = []
for item in list1:
for other in list2:
if item == other:
common.append(item)
return common
def find_common_fast(list1, list2):
"""O(n) — use a set for O(1) lookup"""
set2 = set(list2)
return [item for item in list1 if item in set2]
# With 10,000 items, naive takes ~100,000,000 comparisons
# fast takes ~20,000 operations

When you need to check membership or find intersections, convert one collection to a set rather than nesting loops.

Comprehensions as Alternatives

List comprehensions can replace simple nested loops more compactly:

# Nested loop version
pairs = []
for x in range(3):
for y in range(3):
if x != y:
pairs.append((x, y))
# Comprehension version
pairs = [(x, y) for x in range(3) for y in range(3) if x != y]
print(pairs)
# [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]

Comprehensions run the same iterations — they are syntactic sugar, not a performance shortcut. But they are often more readable for simple cases.

When Not to Nest

Refactor if: the nested loop could be replaced by a set, a dict lookup, or itertools.product.

Refactor if: the total loop count is large and growing — what works on 100 items may be unacceptably slow on 10,000.

Refactor if: understanding the loop requires reading through three or more levels of indentation.

import itertools
# itertools.product is equivalent to nested for loops
for size, colour in itertools.product(sizes, colours):
print(f"{colour} {size}")

itertools.product is both more readable (its name tells you what it does) and just as fast as the equivalent nested loops.

Best Practices