Home Python Nested Loops in Python Explained — How They Work, When to Use Them, and Mistakes to Avoid

Nested Loops in Python Explained — How They Work, When to Use Them, and Mistakes to Avoid

In Plain English 🔥
Imagine you're a teacher taking attendance at a school with 3 classrooms, each holding 5 students. You walk into classroom 1, call out every student's name (1 through 5), then walk into classroom 2, do it again, then classroom 3. That's a nested loop — an outer loop that moves through the classrooms, and an inner loop that runs through every student inside each one. The inner loop completes its entire job before the outer loop takes its next step.
⚡ Quick Answer
Imagine you're a teacher taking attendance at a school with 3 classrooms, each holding 5 students. You walk into classroom 1, call out every student's name (1 through 5), then walk into classroom 2, do it again, then classroom 3. That's a nested loop — an outer loop that moves through the classrooms, and an inner loop that runs through every student inside each one. The inner loop completes its entire job before the outer loop takes its next step.

Loops are one of the most powerful ideas in programming — they let you repeat a task without writing the same code ten times over. But what happens when your data has layers? A school has classrooms AND students. A cinema has rows AND seats. A spreadsheet has rows AND columns. That's where nested loops come in, and they show up constantly in real software.

What Is a Nested Loop — and Why Does Python Need Them?

A nested loop is simply a loop that lives inside another loop. Python runs the outer loop once, then runs the entire inner loop from start to finish, then goes back and advances the outer loop by one step, then runs the entire inner loop again — and so on until both loops are done.

Think of it like a clock. The minute hand (inner loop) has to complete a full 60-minute revolution before the hour hand (outer loop) moves forward by one hour. The inner loop always finishes completely before the outer loop takes its next tick.

You need nested loops whenever your problem has two dimensions — two things you need to iterate over together. Printing a multiplication table, searching a grid, processing a list of lists, or generating coordinates on a map — all of these need nested loops.

Python's indentation makes nested loops visually obvious. The inner loop's code is indented further than the outer loop, so you can see the structure at a glance. That clarity is one of Python's greatest strengths.

nested_loop_basics.py · PYTHON
12345678910111213141516171819
# We have 3 school buildings, each with 4 classrooms.
# We want to visit every classroom in every building.

number_of_buildings = 3
classrooms_per_building = 4

# The outer loop moves through each building one at a time.
for building_number in range(1, number_of_buildings + 1):

    print(f"--- Entering Building {building_number} ---")

    # The inner loop runs fully for EVERY single iteration of the outer loop.
    # So for Building 1, we visit all 4 classrooms before moving to Building 2.
    for classroom_number in range(1, classrooms_per_building + 1):

        # This line runs building_number * classrooms_per_building times total = 12 times.
        print(f"  Visiting Classroom {classroom_number} in Building {building_number}")

print("\nAll classrooms visited!")
▶ Output
--- Entering Building 1 ---
Visiting Classroom 1 in Building 1
Visiting Classroom 2 in Building 1
Visiting Classroom 3 in Building 1
Visiting Classroom 4 in Building 1
--- Entering Building 2 ---
Visiting Classroom 1 in Building 2
Visiting Classroom 2 in Building 2
Visiting Classroom 3 in Building 2
Visiting Classroom 4 in Building 2
--- Entering Building 3 ---
Visiting Classroom 1 in Building 3
Visiting Classroom 2 in Building 3
Visiting Classroom 3 in Building 3
Visiting Classroom 4 in Building 3

All classrooms visited!
🔥
The Golden Rule of Nested Loops:If the outer loop runs M times and the inner loop runs N times, the inner loop's body executes M × N times in total. Always count this way when you're trying to understand or predict a nested loop's behaviour.

Building a Real Example — A Cinema Seat Selector

Let's make this concrete with something everyone understands: a cinema. A cinema has rows (let's say A, B, C) and each row has numbered seats (1 through 5). Every combination of row and seat is a unique ticket — 'A1', 'A2', 'B3', and so on.

Generating every possible seat is a perfect two-dimensional problem. The outer loop walks through the rows. The inner loop walks through the seat numbers within that row. Together they produce every seat combination without us having to write it out by hand.

Notice how the variable from the outer loop (the row letter) is used inside the inner loop. That's the real power of nesting — the inner loop has access to the outer loop's current value and can use it to build something meaningful.

This pattern — combining one variable from the outer loop with one from the inner loop — is the bread and butter of nested loops. You'll use it constantly.

cinema_seat_selector.py · PYTHON
123456789101112131415161718192021222324252627
# A cinema with rows A, B, C and seats 1 through 5.
# We want to print every available seat in the cinema.

row_labels = ['A', 'B', 'C']       # Each letter is one row of seats.
seats_per_row = 5                  # Every row has exactly 5 seats.

print("Available Seats:")
print("----------------")

# Outer loop: move through each row one at a time.
for row_letter in row_labels:

    # Inner loop: for THIS row, go through every seat number.
    for seat_number in range(1, seats_per_row + 1):

        # We combine the outer loop's variable (row_letter) with
        # the inner loop's variable (seat_number) to build a seat label.
        seat_label = f"{row_letter}{seat_number}"

        # end=' ' keeps everything on one line per row — cleaner output.
        print(seat_label, end='  ')

    # After all seats in a row are printed, drop to a new line.
    print()  # This print() runs once per outer loop iteration.

print("----------------")
print(f"Total seats: {len(row_labels) * seats_per_row}")
▶ Output
Available Seats:
----------------
A1 A2 A3 A4 A5
B1 B2 B3 B4 B5
C1 C2 C3 C4 C5
----------------
Total seats: 15
⚠️
Pro Tip — Use the Outer Variable Inside the Inner Loop:The inner loop can read and use the outer loop's variable freely. If you're not combining both loop variables somewhere in your inner loop's body, ask yourself whether you actually need a nested loop at all — you might be overcomplicating things.

A Classic Interview Example — The Multiplication Table

The multiplication table is the go-to nested loop exercise in interviews and coding tests, and for good reason — it perfectly demonstrates two-dimensional iteration. Every cell in the table is the product of a row number and a column number. Row 3, Column 4 = 12. That's it.

The outer loop controls which row we're on (the number we're multiplying by). The inner loop controls which column we're in (the number being multiplied). For every row, we multiply the row number by every column number from 1 to 10.

The formatting here is important too. We use Python's f-string alignment feature to make the numbers line up neatly in columns. This shows that nested loops aren't just about generating data — they're also about presenting it well.

If an interviewer asks you to write a multiplication table with a nested loop, they're checking that you understand that the outer loop variable changes slowly and the inner loop variable changes quickly.

multiplication_table.py · PYTHON
12345678910111213141516171819202122232425262728293031323334
# Print a 5x5 multiplication table.
# The outer loop is the row (multiplier 1-5).
# The inner loop is the column (multiplier 1-5).

table_size = 5

print("Multiplication Table (5x5)")
print("=" * 30)

# Print column headers so the table is readable.
print("    ", end='')   # Leading space to align with row labels.
for column_header in range(1, table_size + 1):
    # :>5 right-aligns the number in a 5-character-wide space.
    print(f"{column_header:>5}", end='')
print()  # New line after headers.
print("-" * 30)

# Outer loop: each row represents one multiplier (1, 2, 3, 4, 5).
for row_multiplier in range(1, table_size + 1):

    # Print the row label on the left.
    print(f"{row_multiplier:>3} |", end='')

    # Inner loop: multiply the row number by every column number.
    for column_multiplier in range(1, table_size + 1):

        result = row_multiplier * column_multiplier   # The actual calculation.

        # :>5 right-aligns each result so columns stay neat.
        print(f"{result:>5}", end='')

    print()  # Move to the next line after all columns in this row are done.

print("=" * 30)
▶ Output
Multiplication Table (5x5)
==============================
1 2 3 4 5
------------------------------
1 | 1 2 3 4 5
2 | 2 4 6 8 10
3 | 3 6 9 12 15
4 | 4 8 12 16 20
5 | 5 10 15 20 25
==============================
🔥
Interview Gold:When asked about nested loop time complexity, the answer is O(n²) when both loops run n times. For a 5x5 table that's 25 operations. For a 1000x1000 grid that's 1,000,000 — which is why interviewers care about when NOT to use nested loops too.

Nested Loops with Lists of Lists — Real Data Structures

In real Python programs, you'll often encounter a list of lists — sometimes called a 2D list or a matrix. Picture a spreadsheet: the outer list is all the rows, and each inner list is the data in one row. Nested loops are the natural way to visit every single cell.

This pattern comes up constantly: student grade books, game boards, pixel grids in images, seating charts, survey results. Any time data has rows and columns, you're looking at a list of lists, and nested loops are your tool.

The key insight here is that the outer loop gives you each inner list (a full row), and the inner loop gives you each individual item within that row. You're peeling the data structure apart layer by layer.

You can also use enumerate() if you need to know which row number or column number you're on — that's a very common pattern in production code.

student_gradebook.py · PYTHON
123456789101112131415161718192021222324252627282930313233343536
# A gradebook stored as a list of lists.
# Each inner list is one student's scores across 3 subjects.

student_names = ["Alice", "Ben", "Clara"]
subject_names = ["Maths", "Science", "English"]

# Each inner list holds [maths_score, science_score, english_score]
gradebook = [
    [88, 92, 75],   # Alice's scores
    [70, 85, 91],   # Ben's scores
    [95, 78, 88],   # Clara's scores
]

print("Student Report Cards")
print("=" * 35)

# Outer loop: enumerate() gives us both the index AND the inner list.
# student_index lets us look up the student's name from student_names.
for student_index, student_scores in enumerate(gradebook):

    student_name = student_names[student_index]  # Get the name by index.
    print(f"\n{student_name}'s Grades:")

    total_score = 0

    # Inner loop: go through each score within this student's row.
    # zip() pairs each score with the matching subject name.
    for subject, score in zip(subject_names, student_scores):
        print(f"  {subject}: {score}/100")
        total_score += score  # Accumulate for the average.

    # This average is calculated once per student — after the inner loop finishes.
    average_score = total_score / len(subject_names)
    print(f"  Average: {average_score:.1f}/100")

print("\n" + "=" * 35)
▶ Output
Student Report Cards
===================================

Alice's Grades:
Maths: 88/100
Science: 92/100
English: 75/100
Average: 85.0/100

Ben's Grades:
Maths: 70/100
Science: 85/100
English: 91/100
Average: 82.0/100

Clara's Grades:
Maths: 95/100
Science: 78/100
English: 88/100
Average: 87.0/100

===================================
⚠️
Pro Tip — zip() is Your Best Friend in Nested Loops:When you have two lists that correspond to each other (like scores and subject names), use zip() to pair them up cleanly inside the inner loop. It's more readable than using an index variable, and it's considered the Pythonic way to do it.
AspectSingle LoopNested Loop
Use caseOne-dimensional data (a single list, a sequence)Two-dimensional data (grid, list of lists, combinations)
ExamplePrinting every student's name from a listPrinting every seat in every row of a cinema
How many times does the body run?N times (once per item)M × N times (outer iterations × inner iterations)
Time complexity (both loops size n)O(n) — linearO(n²) — quadratic
Risk of slowness on large data?Low — scales wellHigh — doubles in n makes it 4× slower
Indentation depthOne level inTwo levels in (inner code is doubly indented)
Variable accessOnly the loop's own variableInner loop can access outer loop's variable freely
Common Python toolsrange(), enumerate(), zip()range(), enumerate(), zip() — all still apply, just nested

🎯 Key Takeaways

  • The inner loop always completes its entire run before the outer loop advances by one step — like the minute hand completing a full revolution before the hour hand moves.
  • Total iterations = outer loop count × inner loop count. A 10×10 nested loop runs the inner body 100 times — always calculate this before writing nested loops on large data sets.
  • Always use distinct, descriptive variable names in each loop (row/column, building/classroom) — reusing a name like i in both loops is a silent bug that Python won't warn you about.
  • Nested loops are the natural tool for 2D data (grids, lists of lists, coordinate pairs) but become expensive at scale — if both loops grow with your data size n, you're looking at O(n²) performance.

⚠ Common Mistakes to Avoid

  • Mistake 1: Reusing the same variable name in both loops — If you write for i in range(3): and then for i in range(5): inside it, the inner loop overwrites i on every iteration. When the inner loop finishes, i holds the last inner value, not the outer one. Fix it by always using distinct, descriptive names — like for row in range(3): and for column in range(5):. Descriptive names also make the code infinitely easier to read.
  • Mistake 2: Putting logic in the wrong loop — A very common mistake is resetting a counter or printing a summary inside the inner loop when it should run once per outer iteration. For example, if you calculate a student's average inside the inner loop, it recalculates (incorrectly) after every single subject score instead of once after all scores. Fix it by asking: 'Should this happen once per outer item, or once per inner item?' Code that belongs to the outer loop must be at the outer loop's indentation level, not the inner loop's.
  • Mistake 3: Off-by-one errors with range() — Beginners often write range(n) when they mean seats 1 through n, and then wonder why they get 0 through n-1. If you want the numbers 1 to 5, write range(1, 6) — the second argument is exclusive. A fast mental check: range(1, 6) gives you 6 - 1 = 5 numbers. Always verify by mentally running the first and last value through your range before trusting the output.

Interview Questions on This Topic

  • QWhat is the time complexity of a nested loop where the outer loop runs n times and the inner loop also runs n times? Can you explain why, and give a real-world scenario where this matters?
  • QWrite a nested loop in Python that prints a right-angled triangle of stars — 1 star on row 1, 2 stars on row 2, up to 5 stars on row 5. Walk me through your reasoning as you write it.
  • QIf you had a list of lists and needed to find a specific value inside it, how would you approach that with a nested loop? How would you handle breaking out of both loops once you've found the value — and why is a plain `break` statement not enough?

Frequently Asked Questions

How many levels deep can you nest loops in Python?

Python has no hard limit on how many loops you can nest inside each other. However, more than two or three levels deep is usually a sign that your code needs to be refactored — it becomes very hard to read and debug. If you find yourself with four nested loops, consider breaking the inner logic into a separate function.

Can you use a while loop inside a for loop in Python?

Absolutely. Python lets you mix loop types freely. You can put a while loop inside a for loop, a for loop inside a while loop, or any combination. The rules are the same — the inner loop always completes fully before the outer loop moves forward. Just make sure your while loop has a clear exit condition or you'll create an infinite loop.

How do I break out of both a nested loop and its outer loop at the same time in Python?

A plain break only exits the innermost loop it's inside — it doesn't touch the outer loop. The cleanest Python solution is to use a boolean flag variable: set found = True when you want to stop, then check if found: break in the outer loop immediately after the inner loop finishes. Alternatively, you can put the nested loop inside a function and use return to exit everything at once.

🔥
TheCodeForge Editorial Team Verified Author

Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.

← Previousbreak continue pass in PythonNext →match-case Statement in Python 3.10
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged