Topic 2.7
Nested Loops and Patterns
In one line
A loop inside a loop runs the inner loop completely for every pass of the outer loop, so the total work is rows times columns. Nested loops draw grids and tables, and leaving both loops at once needs a flag, a for/else, or (later) a function.
Think of it like this
A clock. For every hour, the minute hand goes all the way around: 60 minutes. Then the hour moves on, and the minute hand goes round again. The outer loop (hours) moves slowly; the inner loop (minutes) runs through all its values for each single step of the outer one.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Nested loop
- A loop inside another loop.
- Outer loop / inner loop
- The outer loop runs the slow, outer cycle; the inner loop runs completely on every outer pass.
- Grid
- Data laid out in rows and columns, like a table or a chessboard.
- Row / column
- A horizontal line of cells / a vertical line of cells.
- end=""
- An option of
printthat makes the next print continue on the same line. - Flag
- A variable such as
foundthat records something happened, so another loop can react to it. - Time cost n × m
- The number of steps of two nested loops: the outer count times the inner count.
- Pattern
- A shape drawn with text, such as a triangle of stars.
Step by step
01The inner loop runs completely each time
Trace it with your finger. row is 1: the inner loop runs col = 1, 2, 3 and prints three numbers. Then row is 2 and the inner loop runs all three columns again. The line print(row, col) runs 3 × 3 = 9 times.
for row in range(1, 4):
for col in range(1, 4):
print(row, col)02A multiplication table
The inner print ends with end="" so the numbers stay on one line. The format :3 pads each number to a width of 3 so the columns line up (Topic 1.9). The bare print() after the inner loop moves to the next line.
for row in range(1, 4):
for col in range(1, 4):
print(f"{row * col:3}", end="")
print()03A pattern without an inner loop
A triangle needs only one loop, because "stars repeated size times" is "*" * size. Always ask whether the inner loop is really needed.
for size in range(1, 5):
print("*" * size)04break only leaves the inner loop
When j reaches 1 the inner loop breaks, but the outer loop carries on with the next i. That is why "pair 0 0", "pair 1 0" and "pair 2 0" all appear.
for i in range(3):
for j in range(3):
if j == 1:
break
print("pair", i, j)05Leaving both loops with a flag
Search a grid (a list of lists: each row is itself a list) for the value 5. When the inner loop finds it, it records the position as a pair such as (1, 1) and breaks. The outer loop sees that found is no longer None and breaks too. enumerate supplies row and column numbers (Topic 2.6).
grid = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
target = 5
found = None
for r, row in enumerate(grid):
for c, value in enumerate(row):
if value == target:
found = (r, c)
break
if found is not None:
break
print("Found at", found) # Found at (1, 1)06Counting the cost
Count the steps instead of imagining them. Three outer passes times four inner passes is twelve. If both sizes double, the steps quadruple.
steps = 0
for a in range(1, 4):
for b in range(1, 5):
steps += 1
print("steps:", steps) # 1207A naming trap
If both loops use the same variable, the inner loop overwrites the outer one's variable on every pass. Python allows it without complaint, so give each loop its own name.
for i in range(2):
for i in range(3): # reuses i: confusing
pass
print("outer i is now", i) # 2, not 0 or 1Try it yourself
- 1
Predict the count
How many times does
print("x")run infor i in range(4):withfor j in range(5):inside? Write down the number, then run it with a counter to check (20). - 2
Draw a rectangle
Print a rectangle of
#that is 3 rows high and 5 columns wide, using the inner loop withend="". Then do it again with only one loop and string repetition. - 3
Staircase
Print the numbers 1, 12, 123, 1234 on separate lines (each row longer than the one before). Hint: the outer loop picks the row number, the inner loop goes from 1 up to that number and prints with
end="", and a bareprint()ends each row.
Code & diagrams
Change both ranges to range(1, 6) for a 5 by 5 table. Then try f"{row * col:4}" to widen the columns.
Expected output
1 2 3
2 4 6
3 6 9
*
**
***
****Change target to 9 and see the new position. Change it to 100: what does found stay as?
Expected output
Found at (1, 1)
pair 0 0
pair 1 0
pair 2 0
steps: 12Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Expect break to leave both loops
Write a break in the inner loop and expect the whole search to stop.
for i in range(3):
for j in range(3):
if j == 1:
break
print("pair", i, j)Myth vs fact
Myth
Nested loops are always slow.
Fact
They cost n × m steps, which is fine for small grids and a problem only for large ones. Check the sizes before you worry.
Myth
break in the inner loop stops everything.
Fact
It leaves only the nearest loop. The outer loop continues.
Myth
You need an inner loop to repeat a character.
Fact
String repetition ("*" * 5) does it with no loop.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Total work grows by multiplication, so deeply nested loops over large data are the first thing to look for when a program is slow. Real fixes are a better data structure (a
setordictlookup replaces an inner loop, Topic 4.11), vectorising with NumPy (Topic 14.3), or restructuring the algorithm. The DSA course (/dsa) is built around this idea. - ▸
itertools.product(a, b)(Topic 9.4) replaces two nestedforloops with one, producing every pair, which flattens deep nesting. It does the same number of steps but reads better. - ▸
An inner
for/elsefollowed bycontinue, with abreakafter it in the outer loop, is a known way to break out of two loops without a flag, but a function with areturnis clearer to most readers and is what style guides recommend. - ▸
When you build a grid, write
[[0] * cols for _ in range(rows)].[[0] * cols] * rowsrepeats one inner listrowstimes, so changing one cell changes a whole column (Topic 4.9). - ▸
In AI code, nested Python loops over numbers are a red flag. Matrix and batch maths belongs in NumPy or PyTorch, which run the inner loop in compiled code and are often 100 times faster than the same loops written in plain Python.
Remember this
- 1
A nested loop is a loop whose body contains another loop. Python runs the first outer pass, then the whole inner loop from start to end, then the second outer pass, then the whole inner loop again, and so on. If the outer loop makes 3 passes and the inner makes 4, the innermost line runs
3 × 4 = 12times. - 2
That multiplication is the cost of nesting. Total work is n × m: a 1,000 by 1,000 grid is a million steps. Three nested loops multiply three numbers. When your data gets big, extra levels of nesting are what make a program slow (Topic 4.11 explains Big-O thinking, and the DSA course builds on it).
- 3
Nested loops are the natural tool for anything shaped like a grid: rows and columns in a table, cells on a board, pixels in an image, every pair of items from two lists. A multiplication table is the classic example: the outer loop picks the row, the inner loop walks the columns.
- 4
To print a row on one line, use
print(..., end="")inside the inner loop (so it does not start a new line each time), and a bareprint()after the inner loop to finish the row. Text patterns such as a triangle can do without an inner loop at all, using string repetition:"*" * size. - 5
breakandcontinueapply to the inner loop only (Topic 2.5). To stop both loops, you have three choices. Use a flag (a variable such asfoundthat the outer loop checks). Or use the inner loop'selsewith acontinue/breakpair (an advanced trick). Or, best of all, put the loops in a function andreturn(Topic 5.10). - 6
Do not reuse the same variable name (
i) for both loops. It makes the outer variable misleading. - 7
Data in real programs is often nested too: a list of lists is a grid, and a list of dictionaries is a table. A loop over the rows with an inner loop over the cells is how you process them (Topic 4.8). The same shape appears in AI work: a batch of examples (outer) each with many numbers (inner), which NumPy later does for you in one step.
Explain it without notes
How many times does the innermost statement run in two nested loops of sizes n and m?
How do you print a grid with each row on its own line?
How can you exit two nested loops at once?
Why can reusing the same loop variable in nested loops cause bugs?
Practice
Print a rectangle of # 3 rows high and 5 columns wide using two loops.
Print every pair (i, j) with i and j from 0 to 1, as 0 0, 0 1, 1 0, 1 1, one per line.
Count how many steps a loop of 5 outer and 4 inner passes makes and print Steps: 20.
In numbers = [1, 4, 6, 9], find the first pair a < b (checking a from the list in order, then b in order) whose sum is 10 and print Found: (1, 9). Stop both loops.
Trade-offs
- ↔
Nested loops vs a better data structure. Two loops are the simplest correct solution, but a
setordictoften removes the inner loop and the n × m cost. - ↔
Flag vs function with return. A flag works anywhere. A function with
returnis clearer but needs the function you will learn in Phase 5.
Done when you can
Done when you can trace a nested loop by hand and say how many times the inner line runs.
Done when you can print a grid using
end=""and a bareprint().Done when you can explain which loop a
breakleaves and exit both loops with a flag.Done when you can state the n × m cost and why same-name loop variables are risky.