Python Nested Loops

In the last lesson you learned the pass Statement. That was a tiny placeholder that does nothing on purpose. Now we go the other way. We make a loop do a lot more. Sometimes one loop is not enough. You have a list of rows. Inside each row you have a list of columns. To handle that, you put one loop inside another. That is a nested loop. This lesson shows you how it works.

🤔 Why Nested Loops?

Say you want to print a small grid of stars. Five stars across and three rows down. A single loop can print one row. But on its own it cannot repeat that row three times and reset for each line.

Here is the pain. Anything with rows and columns needs you to loop twice. A calendar, a seating chart, a multiplication table. You loop once for the outer thing. Then you loop again for the thing inside it.

A nested loop solves that. You loop over the rows on the outside. For each row you loop over the columns on the inside.

🧩 What a Nested Loop Is

A nested loop is just a loop written inside the body of another loop.

Here is the idea in plain words:

  • The outer loop starts its first turn.
  • The inner loop then runs all the way through, start to finish.
  • The outer loop takes its next turn.
  • The inner loop runs all the way through again.
  • This keeps going until the outer loop is done.

So the inner loop finishes completely for every single turn of the outer loop. That is the one rule to hold onto.

Here is a real-world way to picture it. Think of a clock. The hour hand is the outer loop. For every one hour it moves, the minute hand goes all the way around the full sixty minutes. The minute hand is the inner loop. It finishes a whole lap before the hour hand moves again.

✍️ The Basic Shape

The inner loop sits indented one level deeper than the outer loop. That extra indentation is what tells Python “this loop belongs inside the other one.”

This code runs an outer loop and an inner loop. It prints which turn each one is on.

for outer in range(2):
print(f"Outer turn {outer}")
for inner in range(3):
print(f" Inner turn {inner}")

Let’s read it from the top:

  • for outer in range(2): is the outer loop. It takes two turns, outer being 0 then 1.
  • print(f"Outer turn {outer}") runs once at the start of each outer turn.
  • for inner in range(3): is the inner loop. It is indented inside the outer loop, so it runs fully on every outer turn.
  • print(f" Inner turn {inner}") runs three times per outer turn.

Here is exactly what it prints:

Output

Outer turn 0   Inner turn 0   Inner turn 1   Inner turn 2 Outer turn 1   Inner turn 0   Inner turn 1   Inner turn 2

See how the inner counts 0, 1, 2 in full, then resets and counts 0, 1, 2 again? The reset happens because the inner loop starts fresh every time the outer loop comes back around.

🔢 How Many Times Does the Inner Body Run?

This part trips a lot of people up. So let’s make it simple.

The inner body runs the outer count times the inner count. You multiply them.

In the example above, the outer loop ran 2 times and the inner loop ran 3 times. So the inner print ran:

2 * 3 # = 6 times

Count the “Inner turn” lines in the output above. There are six of them. That matches.

Tip

A quick way to check your thinking: outer turns multiplied by inner turns equals total inner runs. If the outer is 4 and the inner is 5, the inner body runs 20 times.

⭐ A Real Example: Printing a Grid

Now let’s print that grid of stars. We want three rows. We want five stars in each row.

This code uses an outer loop for the rows and an inner loop for the stars in one row.

for row in range(3):
for star in range(5):
print("*", end="")
print()

Reading it line by line:

  • for row in range(3): runs once per row, so three times.
  • for star in range(5): runs five times inside each row, printing one star each time.
  • print("*", end="") prints a star with no line break, so the stars stay on the same line. The end="" part replaces the usual newline with nothing.
  • print() runs after the inner loop, on the outer level. It prints an empty line. That ends the current row and moves to the next one.

Here is what you get:

Output




The print() on the outer level is doing important work. It only runs after the inner loop has finished a full row. Move it one indent deeper and every star would land on its own line.

✖️ A More Useful Example: Multiplication Table

Grids are nice. But here is a nested loop you would actually reach for. Let’s print a small multiplication table for the numbers 1 through 3.

This code loops over each number on the outside. It loops over each multiplier on the inside.

for i in range(1, 4):
for j in range(1, 4):
print(f"{i} x {j} = {i * j}")
print("---")

What each piece does:

  • for i in range(1, 4): gives i the values 1, 2, 3. This is the number whose table we are printing.
  • for j in range(1, 4): gives j the values 1, 2, 3 for each i. This is the multiplier.
  • print(f"{i} x {j} = {i * j}") prints one line of the table, working out i * j inside the f-string.
  • print("---") runs on the outer level, after each full table, to separate them.

The output is:

Output

1 x 1 = 1 1 x 2 = 2 1 x 3 = 3

2 x 1 = 2 2 x 2 = 4 2 x 3 = 6

3 x 1 = 3 3 x 2 = 6 3 x 3 = 9

The outer loop ran 3 times. The inner loop ran 3 times for each of those. So the table line printed 3 × 3 = 9 times. The separator printed 3 times, once per outer turn.

🐢 A Heads-Up on Speed

Nested loops are handy. But there is a catch worth knowing early.

The work grows fast as the loops get bigger. Remember, the inner body runs outer × inner times. If both loops go up to 1,000, that is one million runs. Push both to 10,000 and you are at a hundred million.

This table shows how quickly the inner runs pile up:

Outer count Inner count Inner body runs
10 10 100
100 100 10,000
1,000 1,000 1,000,000

You do not need to fear nested loops. For small jobs like a grid or a table, they are perfect. Just keep this growth in mind. So when something feels slow with big inputs, you know where to look first.

⚠️ Common Mistakes

A few slip-ups come up again and again with nested loops. Watch for these.

Wrong indentation. The line you want after the inner loop must sit at the outer level. Put it one indent too deep and it runs inside the inner loop instead.

# ❌ Avoid: print() is inside the inner loop, so each star gets its own line
for row in range(3):
for star in range(5):
print("*", end="")
print()
# ✅ Good: print() is on the outer level, so it ends one full row
for row in range(3):
for star in range(5):
print("*", end="")
print()

Reusing the same loop variable. The outer and inner loops should use different variable names. If both use i, the inner loop overwrites the outer value and your logic breaks.

# ❌ Avoid: both loops use i, so the inner one overwrites the outer one
for i in range(3):
for i in range(3):
print(i)
# ✅ Good: separate names keep each loop's value safe
for row in range(3):
for col in range(3):
print(row, col)

Forgetting the inner loop runs in full every time. People often expect the inner loop to pick up where it left off. It does not. It starts over from the beginning on every outer turn.

✅ Best Practices

  • Give your loop variables clear names like row and col, or i and j for short. Never the same name for both.
  • Keep nesting shallow. Two levels is common and easy to read. Three or more gets hard to follow fast, so look for another way if you reach that point.
  • Put any line that should run once per outer turn at the outer level, lined up with the inner loop, not inside it.
  • If the inner loop body does the same heavy work every time, see whether you can compute it once before the loops instead.

🧩 What You’ve Learned

  • ✅ A nested loop is a loop inside the body of another loop.
  • ✅ The inner loop runs fully for every single turn of the outer loop.
  • ✅ The inner body runs outer count × inner count times in total.
  • ✅ Nested loops are a natural fit for rows-and-columns work like grids and multiplication tables.
  • ✅ A line placed at the outer level runs once per outer turn, which is how you end each row.
  • ✅ The work grows fast with big loops, so keep an eye on speed when inputs get large.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    In a nested loop, how many times does the inner loop run for each turn of the outer loop?

    Why: The inner loop runs from start to finish again for every single turn of the outer loop.

  2. 2

    If the outer loop runs 4 times and the inner loop runs 5 times, how many times does the inner body run in total?

    Why: You multiply the counts: 4 outer turns times 5 inner runs each equals 20.

  3. 3

    In a grid-printing nested loop, where should print() go to end each row?

    Why: Placed at the outer level after the inner loop, print() ends one full row before the next outer turn.

  4. 4

    Why should the outer and inner loops use different variable names?

    Why: Sharing a name means the inner loop overwrites the outer loop's value, which breaks the logic.

🚀 What’s Next?

You can now loop inside a loop and walk through anything shaped like rows and columns. Next we’ll look at common shapes these loops take so you can spot and reuse them.

Loop Patterns

Share & Connect