Nested Loops: Patterns, Performance, and When to Avoid Them
Think about a spreadsheet. It has rows and columns. To read every cell, you go through each row, and for each row, you go through each column. That's two loops — one inside the other. That's a nested loop.
Nested loops show up everywhere in programming: printing grid patterns, searching through tables, generating combinations, and processing images pixel by pixel. They're powerful, but they can also be surprisingly slow if you're not careful.
In this tutorial, you'll learn how nested loops work, see practical examples with grids and combinations, understand why they can be slow, and discover when to use alternatives instead.
How Do Nested Loops Work in Python?
A nested loop is simply a loop inside another loop. The outer loop runs first. For each iteration of the outer loop, the inner loop runs completely from start to finish.
Here's the key insight: if the outer loop runs n times and the inner loop runs m times, the code inside the inner loop executes n x m times total. Three rows and three columns means 9 total prints.
Working with 2D Data (Grids and Tables)
The most practical use of nested loops is working with two-dimensional data. A list of lists in Python represents a grid, table, or matrix. The outer loop goes through the rows, and the inner loop goes through the columns in each row.
Sometimes you need the row and column index, not just the values. Use enumerate() for that.
Here's a practical example: building a multiplication table.
Generating Combinations with Nested Loops
Nested loops are perfect for generating every combination of items from two (or more) groups. The outer loop picks from the first group, and the inner loop picks from the second.
With 2 colors and 3 sizes, you get 6 combinations. Every color is paired with every size. This is called the Cartesian product of the two lists.
Building Patterns with Nested Loops
Pattern printing is a classic exercise for understanding nested loops. Each row is controlled by the outer loop, and the number of characters per row is controlled by the inner loop.
The trick is that the inner loop's range depends on the outer loop's variable. In row 1, the inner loop runs once. In row 2, twice. In row 5, five times. That creates the staircase shape.
Performance: Why Nested Loops Can Be Slow
A single loop over 1,000 items runs 1,000 times. A nested loop over 1,000 items runs 1,000 x 1,000 = 1,000,000 times. That's a million operations. Add a third level of nesting and you hit a billion. Nested loops multiply, and the numbers grow fast.
Computer scientists describe this with Big O notation. A single loop is O(n) — the time grows linearly with the data size. A nested loop is O(n²) — the time grows with the square of the data size. Double the input and the work quadruples.
When to Avoid Nested Loops
Not every problem that looks like it needs nested loops actually does. Often there's a faster approach using sets, dictionaries, or built-in functions.
nums = [3, 1, 4, 1, 5, 9]
has_dup = False
for i in range(len(nums)):
for j in range(i+1, len(nums)):
if nums[i] == nums[j]:
has_dup = True
print(has_dup) # Truenums = [3, 1, 4, 1, 5, 9]
has_dup = len(nums) != len(set(nums))
print(has_dup) # TrueThe set version is not just shorter — it's dramatically faster. For 10,000 items, the nested loop version does 50 million comparisons. The set version does about 10,000 operations. That's 5,000 times faster.
students = [
{'name': 'Alice', 'id': 101},
{'name': 'Bob', 'id': 102},
{'name': 'Charlie', 'id': 103}
]
ids_to_find = [103, 101]
for target_id in ids_to_find:
for s in students:
if s['id'] == target_id:
print(s['name'])students = [
{'name': 'Alice', 'id': 101},
{'name': 'Bob', 'id': 102},
{'name': 'Charlie', 'id': 103}
]
ids_to_find = [103, 101]
lookup = {s['id']: s['name'] for s in students}
for target_id in ids_to_find:
print(lookup[target_id])Practice Exercises
These exercises will strengthen your understanding of nested loops. They start with basic patterns and move toward real-world problems.
Print a rectangle of stars with 3 rows and 5 columns. Each row should have 5 stars with no spaces between them.
Expected output:
*****
*****
*****Without running the code, predict what it will print. Count the total number of lines in the output. Then run it to check.
Print a number triangle where each row contains the row number repeated. The triangle has 4 rows.
Expected output:
1
22
333
4444A grid of numbers is given. Use nested loops to calculate and print the sum of all values in the grid.
Expected output:
45Given a list of tops and bottoms, print every possible outfit combination followed by the total count.
Expected output:
shirt + jeans
shirt + shorts
sweater + jeans
sweater + shorts
hoodie + jeans
hoodie + shorts
Total outfits: 6This code is supposed to print a 3x3 coordinate grid, but it has a bug. The output should show each coordinate on its own line, with x going from 0 to 2 and y going from 0 to 2.
Expected output:
(0,0) (0,1) (0,2)
(1,0) (1,1) (1,2)
(2,0) (2,1) (2,2)Find and fix the bug.
Summary: Python Nested Loops
| Here's what you learned about nested loops: | |
|---|---|
| --- | --- |
| Nested loop | A loop inside another loop |
| Total iterations | Outer count x Inner count |
| 2D data | Outer loop = rows, inner loop = columns |
| Combinations | Pair every item from group A with every item from group B |
| Performance | O(n²) — doubles the input, quadruples the work |
| Alternatives | Sets and dictionaries often replace nested loops |
Rules of thumb:
'*' * n) often replaces inner loops for patternsWhat's Next?
In the next tutorial — [How to Read Python Error Messages](/python/python-error-messages) — you'll learn to decode Python's error messages, fix the most common bugs, and stop fearing tracebacks. Debugging is a skill that makes everything else easier.