Mastering Nested Loops
Introduction: When One Loop Is Not Enough
Welcome back to Solving Problems with Loop Patterns in Python! We are now halfway through the course and beginning the third of four units. Unit 1 gave us search and aggregation patterns, and Unit 2 taught us how to grow a brand-new list with append(). Every one of those patterns had something in common: a single loop walking through a single sequence, exactly one time.
Some questions need a different shape. A clothing shop needs every shirt size paired with every available color; a tournament needs every player matched against every opponent; a spreadsheet needs every row combined with every column. Two sequences are in play at once. To generate every pair directly using basic loops, we place one loop inside another.
Here is the data we will work with in this lesson, along with the output we are building toward:
The core idea is short enough to state in one sentence: a loop body can contain another loop, and that inner loop runs from start to finish on every single pass of the outer loop.
The Shape of a Nested Loop
Before adding any real work, let us look at the bare structure on its own. Here is a nested loop that only prints the pair it is currently looking at:
The anatomy is worth naming carefully:
- The outer loop is the first
forline; it walks throughsizes. - The inner loop is the second
forline; it is simply a statement sitting inside the outer loop's body, just as anifstatement sat inside a loop body in the previous lesson. - The innermost body is indented twice and runs once per pair.
Indentation is the only thing that makes a loop "inner" here, so it deserves close attention. Notice also that the two loops use different variable names, size and color: reusing one name would let the inner loop quietly overwrite the outer one's value.
Tracing the Execution Order
Rules about nesting stick much better once we watch a run unfold. Let us trace the skeleton above, one line of output at a time:
| Outer pass | size | color | Printed |
|---|---|---|---|
| 1 | "S" | "red" | S red |
| 1 | "S" | "blue" | S blue |
| 2 | "M" | "red" | M red |
| 2 | "M" | "blue" | M blue |
| 3 | "L" | "red" | L red |
| 3 | "L" | "blue" | L blue |
Three observations explain everything we will see later:
- The outer variable stays frozen while the inner loop cycles through all of its values.
- The inner sequence restarts from its first item on every outer pass; it does not pick up where it left off.
- The outer variable advances only after the inner loop has completely finished.
Counting Iterations: The Multiplication Rule
From Printing to Collecting: Building the Combinations List
Printing pairs is useful for looking at them, but we usually want to keep them. Let us combine nesting with the build-a-list skeleton from the previous lesson:
Two placement decisions carry all the weight here. First, combinations = [] sits outside both loops; writing it inside the outer loop would throw away the pairs collected on earlier passes. Second, append() sits at the deepest level, so it fires once per pair rather than once per size. The expression size + "-" + color joins the two strings with a dash, producing values like "S-red".
Watching the list grow makes the order obvious: after the first outer pass it holds ["S-red", "S-blue"]; after the second, ["S-red", "S-blue", "M-red", "M-blue"]; and it finishes with six items.
Reading the Results: Order and Totals
With the list built, a plain single loop is enough to display it, and len() reports how many pairs we produced:
Notice how the ordering mirrors the nesting: every color for "S" appears before any color for "M". Swapping which list is outer would change that order, but not the contents. And len(combinations) gives 6, an independent confirmation of our 3 × 2 rule. Here is the complete program:
Common Mistakes with Nested Loops
Conclusion and Next Steps
Three ideas carry this unit. Nesting is nothing exotic: it is a loop written inside another loop's body, with indentation doing the work. The inner loop restarts and completes in full on every outer pass, which freezes the outer variable while the inner one cycles. And when the inner sequence has the same length on every outer pass, the innermost body runs outer-count × inner-count times, a rule that both predicts our results and verifies them afterward.
Your practice is coming up next: pairing two lists into a collection of combinations, reporting the total number of pairs produced, pairing number ranges in a multiplication-style layout, and labeling each inner pass to keep track of how many times the body really runs. Before writing each solution, ask the same two questions: Which sequence should be on the outside? and How deep does this line need to be indented?
After that, we will put nesting to work on grids of data stored as lists of lists, where the outer loop walks the rows and the inner loop walks the cells in each row. Time to write some nested loops of your own!
