Back to Blog
Python

Python Nested For Loop: Syntax and Performance

Practical guide to python nested for loops: syntax, use cases, performance implications, and how to avoid common mistakes.

Pythonloopsiterationperformancecode patterns
Illustration of a nested for loop in Python showing two loops, one inside the other, with a grid of coordinates representing iterations.

A Python nested for loop places one for statement inside another. The inner loop runs to completion for every iteration of the outer loop. This structure is common when working with multidimensional data, generating combinations, or comparing elements across two collections. Understanding how the loops interact is essential for writing correct and efficient code.

Basic Structure of a Nested For Loop

The simplest form of a nested loop is a for statement inside another for statement. The inner loop repeats its entire body for each value produced by the outer loop.

for i in range(3): for j in range(2): print(i, j)

This prints six lines: 0 0, 0 1, 1 0, 1 1, 2 0, 2 1. The outer loop variable i stays fixed while the inner loop iterates through all values of j. Once the inner loop finishes, the outer loop advances to the next i and the inner loop starts over.

The same pattern works with any iterable, not just range. Lists, tuples, strings, and generator expressions can all be used in either loop.

When a Nested Loop Is the Right Tool

Nested loops are a natural fit when the problem involves two independent sequences that must be combined or compared. Common scenarios include:

  • Traversing a 2D matrix or grid, where the outer loop selects the row and the inner loop selects the column.
  • Generating all pairs from two lists, such as checking every product against every supplier.
  • Building a Cartesian product of small collections when the full result is needed.

For example, to print the coordinates of a 3x3 grid:

for row in range(3): for col in range(3): print(f"({row}, {col})")

This works because each cell is uniquely identified by its row and column indices. A single loop cannot produce that pairing directly without extra arithmetic or data structures.

Reading Order and Variable Scope

Execution order matters. The outer loop starts first, and the inner loop runs to completion before the outer loop moves to its next iteration. The inner loop body sees the current value of the outer variable. Changing the inner loop variable does not affect the outer loop's progress, because the outer for controls its own iteration.

Variable scope in Python is function-level, not block-level. A variable defined inside a loop is still accessible after the loop finishes. That can be useful, but reusing the same variable name in nested loops can cause subtle confusion. For example:

items = [1, 2, 3] for item in items: for item in items: print(item)

Here the inner loop overwrites the outer item variable. Python's for loop does not use the loop variable to remember its position, so the outer loop will still move to the next element. But after the inner loop finishes, item holds the last value from the inner loop instead of the current outer value. That is easy to misuse later. Using distinct names like outer_item and inner_item avoids the confusion.

Common Pitfalls in Nested Loops

One frequent mistake is modifying a list while iterating over it inside a nested loop. Removing elements during iteration shifts indices and can cause elements to be skipped or processed twice. If removal is necessary, collect the indices first or build a new list.

Another issue is using the same loop variable name in both levels, as shown above. Most linters will flag this, but it is easy to miss in a long function.

A third pitfall is accidentally creating an infinite loop when the inner loop iterates over a mutable collection that changes. For instance, appending to a list while iterating over it can extend the iteration indefinitely. Always verify that the inner loop has clear termination behavior independent of the outer loop's side effects.

Performance Considerations

The number of inner-loop iterations is the product of the iteration counts when both ranges are fixed. If the outer loop runs n times and the inner loop runs m times, the inner body executes n * m times. If the inner range depends on the outer variable, compute the sum of the range sizes instead of treating it as a fixed product. For large n and m, the time can grow quickly.

Reducing the number of inner iterations is often more effective than micro-optimizing the loop body. For example, when comparing elements in a single list, you can avoid duplicate and self-pairs by starting the inner loop at the outer index plus one:

items = [3, 1, 4, 1, 5] for i in range(len(items)): for j in range(i + 1, len(items)): print(items[i], items[j])

This checks each unordered pair once: n(n-1)/2 comparisons instead of n^2. The principle is general: eliminate redundant work before optimizing loop mechanics.

Memory usage is usually not a concern for the loop itself, but building large lists inside the inner loop can consume significant memory. If you only need to process each pair and discard it, use a generator expression or process values directly instead of accumulating them.

Alternatives to Nested Loops

Python's standard library provides tools that can replace nested loops in specific situations. itertools.product generates the Cartesian product of iterables without explicit nesting:

from itertools import product for a, b in product(range(3), range(2)): print(a, b)

This is clearer when the nesting is purely combinatorial and you do not need the outer loop variable to control the inner loop's range. It also avoids variable shadowing because each loop variable is distinct.

List comprehensions can also flatten a nested loop into a single expression. For example, building a list of coordinates:

coords = [(x, y) for x in range(3) for y in range(2)]

The comprehension evaluates the loops in the same order as a nested for statement: the first for is the outer loop, and the second is the inner loop. This is concise, but it can become hard to read when the logic is complex. Use it only when the expression is simple and the intent is clear.

Breaking Out of Nested Loops

A break statement inside the inner loop only exits that loop, not the outer one. If you need to stop both loops when a condition is met, you must either use a flag variable or raise an exception. A flag is straightforward:

found = False for i in range(10): for j in range(10): if i * j == 42: found = True break if found: break

After the inner break, the code checks found and breaks the outer loop. This works but adds an extra conditional. Another option is to return from a function, which exits all loops at once. For deeply nested loops, moving the logic into a helper function is often cleaner than managing multiple flags.

Realistic Example: Matrix Transposition

A nested loop is the direct way to transpose a matrix represented as a list of lists. The outer loop iterates over columns, and the inner loop iterates over rows:

def transpose(matrix): rows = len(matrix) cols = len(matrix[0]) result = [[0] * rows for _ in range(cols)] for i in range(rows): for j in range(cols): result[j][i] = matrix[i][j] return result

This works because each element matrix[i][j] moves to result[j][i]. The nested loop visits every cell exactly once. The time complexity is O(rows * cols), which is optimal for this operation because every element must be read and written. A single loop would require manual index arithmetic and would not be clearer.

Edge Cases

A few edge cases are worth noting. If the outer iterable is empty, the inner loop never runs. If the inner iterable is empty, the outer loop still runs but does nothing for that iteration. This is usually harmless, but it can mask logic errors if you expect a certain number of inner iterations.

When the inner loop depends on the outer loop's variable, the inner iterable must be recreated each time. For example, for j in range(i) creates a new range object with a different length for each i. This is expected behavior, but be aware that the inner loop's total work is the sum of i values, not a fixed product.

If you need an index while iterating over a collection, use enumerate in the outer loop and pass the index to the inner loop. This is idiomatic and avoids manual counter management.

Finally, nested loops can be combined with else clauses, but the behavior can be surprising. The else block after a for loop runs only if the loop completes without a break. In a nested loop, the else applies to the loop it is attached to, not to the outer loop. Test this carefully if you rely on it, because it is easy to misinterpret which loop the else belongs to.

Python Nested For Loop: Syntax, Performance, and Common Pitfalls | RYUSLOG DEV