PeakblickYou can put more than one for in a list comprehension to get the effect of nested loops — but the...
You can put more than one for in a list comprehension to get the effect of nested loops — but the order of those for clauses trips almost everyone up the first time. Here's how it actually works, with the one rule that makes it click.
A comprehension with two for clauses behaves exactly like two nested for loops, and — this is the whole trick — the clauses read left to right in the same order you'd write the loops: outer loop first, inner loop second.
pairs = [(x, y) for x in range(3) for y in range(3)]
# [(0,0), (0,1), (0,2), (1,0), (1,1), (1,2), (2,0), (2,1), (2,2)]
That single line is identical to:
pairs = []
for x in range(3): # outer -> first for-clause
for y in range(3): # inner -> second for-clause
pairs.append((x, y))
So the mental model is simple: copy the loops top to bottom, then the expression ((x, y)) goes at the front. The inner loop runs fully for each step of the outer one, just like real nested loops.
The instinct is to write the clauses "inside out," but Python reads them outside in. Each clause can only use variables defined by a clause to its left. So this fails:
# WRONG — 'row' is used before it's defined
flat = [n for n in row for row in matrix]
# NameError: name 'row' is not defined
The fix is to put the clauses in loop order — outer (row) first, then inner (n):
# RIGHT
flat = [n for row in matrix for n in row]
If you ever get a NameError in a comprehension, it's almost always this: a later variable used in an earlier clause. Reorder them the way you'd nest the loops.
That last example is the most common real use of nested loops in a comprehension — turning a list of lists into one flat list:
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [n for row in matrix for n in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
Read it as "for each row in the matrix, for each n in that row, take n." Outer first, inner second — same rule.
This is a different thing, and it's worth not confusing the two. Nested for clauses flatten; a comprehension inside another comprehension builds structure — a list of lists.
# build a 2x3 grid
grid = [[col for col in range(3)] for row in range(2)]
# [[0, 1, 2], [0, 1, 2]]
Here the inner [col for col in range(3)] produces one row, and the outer comprehension repeats it. A classic use is transposing a matrix (swapping rows and columns):
matrix = [[1, 2, 3], [4, 5, 6]]
transposed = [[row[i] for row in matrix] for i in range(len(matrix[0]))]
# [[1, 4], [2, 5], [3, 6]]
Rule of thumb: multiple for clauses in one comprehension → a flat list; a comprehension nested in brackets → a list of lists.
You can filter with if after the for clauses, and the condition can use any variable defined to its left:
# all pairs where the two numbers differ
pairs = [(x, y) for x in range(3) for y in range(3) if x != y]
# [(0,1), (0,2), (1,0), (1,2), (2,0), (2,1)]
Multiple conditions just chain: ... if x != y if x + y > 1 keeps only pairs that pass both.
Comprehensions are great until they aren't readable. One or two for clauses to flatten or pair up values is clean Python. Three clauses, or a nested comprehension with conditions inside conditions, is usually the moment to write the plain nested loops instead — they're easier to read, debug, and change. The goal is clarity, not fitting everything on one line.
Quick reference:
[expr for a in outer for b in inner] # nested loops, flat result
[expr for a in outer for b in inner if c] # with a filter
[[expr for b in inner] for a in outer] # nested comprehension -> list of lists
Nested comprehensions and the for-clause order come up constantly in Python interviews — see the Python interview questions for the data-model ideas behind them.
Prepping for a Python interview? Peakblick runs mock interviews on a timer and scores every answer 1–10 with feedback — so you find the weak spots before the real thing. Free to try, no card.