Python Nested List Comprehensions Explained (Python Docs 5.1.4) | Matrix Transposition, zip() & Beginner Guide
This page explains Python Nested List Comprehensions (Python Docs 5.1.4) in simple language. A nested list comprehension is a list comprehension that has another list comprehension as its expression โ it's the most concise way to work with 2D data like matrices in Python.
The docs use matrix transposition as the main example. By the end of this guide you'll understand what nested comprehensions actually do under the hood, how they compare to equivalent nested loops, and when to use zip() instead.
What is a Python Nested List Comprehension?
A regular list comprehension creates a flat list. A nested list comprehension has another list comprehension as its expression โ so it creates a list of lists. This is exactly what you need for 2D structures like matrices, grids, or tables.
The initial expression in a list comprehension can be any arbitrary expression, including another list comprehension.
The syntax looks like this:
# Structure of a nested list comprehension
[[inner_expression for item in inner_iterable] for item in outer_iterable]
# โ inner comprehension โ โ outer for loop โ
# The outer for loop runs first, producing one row per iteration.
# For each row, the inner comprehension builds a list.
The Matrix Example โ From the Python Docs
The Python docs use a 3ร4 matrix to demonstrate nested comprehensions. A 3ร4 matrix has 3 rows and 4 columns:
matrix = [
[1, 2, 3, 4], # row 0
[5, 6, 7, 8], # row 1
[9, 10, 11, 12], # row 2
]
Transposing means flipping rows and columns โ row 0 becomes column 0, row 1 becomes column 1, and so on. Watch the highlighted numbers: the first row of the original matrix becomes the first column of the transposed matrix.
# Transpose with a nested list comprehension
[[row[i] for row in matrix] for i in range(4)]
# [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
Three Equivalent Ways to Transpose โ All from the Python Docs
The docs show the same operation three ways, from the most verbose to the most concise. Understanding all three is key to really getting nested comprehensions:
[[row[i] for row in matrix] for i in range(4)]
# [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
transposed = []
for i in range(4):
transposed.append([row[i] for row in matrix])
transposed
# [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
transposed = []
for i in range(4):
transposed_row = []
for row in matrix:
transposed_row.append(row[i])
transposed.append(transposed_row)
transposed
# [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
All three give identical results. Version 1 is the most Pythonic, but Version 3 is the easiest to trace through step by step if you're learning.
How the Inner Comprehension Evaluates โ Step by Step
This is the part that confuses most beginners. The inner comprehension doesn't run independently โ it's evaluated in the context of the outer for loop.
The inner list comprehension is evaluated in the context of the for that follows it.
The outer loop runs first: for i in range(4) gives i = 0, then 1, then 2, then 3.
For each value of i, the inner comprehension [row[i] for row in matrix] runs completely โ collecting column i from every row.
When i = 0: [row[0] for row in matrix] โ [1, 5, 9] (first element of each row).
When i = 1: [row[1] for row in matrix] โ [2, 6, 10] (second element of each row).
Each result is one row of the transposed matrix. The outer comprehension collects all four into a list of lists.
matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
# Trace what happens at each step of the outer loop:
# i = 0 โ [row[0] for row in matrix] โ [1, 5, 9]
# i = 1 โ [row[1] for row in matrix] โ [2, 6, 10]
# i = 2 โ [row[2] for row in matrix] โ [3, 7, 11]
# i = 3 โ [row[3] for row in matrix] โ [4, 8, 12]
# All four collected:
result = [[row[i] for row in matrix] for i in range(4)]
print(result)
# [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
zip() โ The Pythonic Shortcut
The docs make an important point here: for matrix transposition specifically, zip() is cleaner and faster. Understanding when to use a built-in instead of a comprehension is a key Python skill.
In the real world, you should prefer built-in functions to complex flow statements. The zip() function would do a great job for this use case.
matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
# zip(*matrix) โ one line, same result
list(zip(*matrix))
# [(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]
# Note: zip gives tuples, not lists.
# If you need lists, wrap with a list comprehension:
[list(row) for row in zip(*matrix)]
# [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
How the * (asterisk) works here
The * in zip(*matrix) is the unpacking operator. It expands the list of rows into separate arguments, as if you'd written zip([1,2,3,4], [5,6,7,8], [9,10,11,12]). zip() then pairs up the first elements, second elements, and so on โ which is exactly transposition.
# These two are identical:
list(zip(*matrix))
list(zip([1,2,3,4], [5,6,7,8], [9,10,11,12]))
# zip() pairs up by position:
# (row0[0], row1[0], row2[0]) โ (1, 5, 9)
# (row0[1], row1[1], row2[1]) โ (2, 6, 10)
# ... and so on
zip(*matrix) for straightforward transposition โ it's faster and more readable. Use a nested comprehension when you need to transform or filter the values as you transpose, not just rearrange them.
Real-World Uses of Nested List Comprehensions
Creating a multiplication table
# 5x5 multiplication table
table = [[row * col for col in range(1, 6)] for row in range(1, 6)]
for row in table:
print(row)
# [1, 2, 3, 4, 5]
# [2, 4, 6, 8, 10]
# [3, 6, 9, 12, 15]
# [4, 8, 12, 16, 20]
# [5, 10, 15, 20, 25]
Flattening a 2D list
# Turn a list of lists into a flat list
nested = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [num for row in nested for num in row]
print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Note: this is a single comprehension with two for clauses,
# not a nested comprehension โ but it's closely related
Filtering a 2D grid
# Keep only even numbers from each row
matrix = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
evens = [[x for x in row if x % 2 == 0] for row in matrix]
print(evens)
# [[2, 4], [6, 8], [10, 12]]
# Replace negatives with 0 across a grid
grid = [[-1, 2, -3], [4, -5, 6], [-7, 8, 9]]
cleaned = [[max(0, x) for x in row] for row in grid]
print(cleaned)
# [[0, 2, 0], [4, 0, 6], [0, 8, 9]]
Common Nested List Comprehension Mistakes
In [[row[i] for row in matrix] for i in range(4)], the outer loop is for i in range(4) (rightmost). Beginners often flip this. Read right to left: outer loop first, inner comprehension second.
[[...] for x in a] is a nested comprehension โ creates a list of lists. [... for x in a for y in b] is a single comprehension with two for clauses โ creates a flat list. They look similar but produce different structures.
For pure transposition, list(zip(*matrix)) is shorter, faster, and more readable than [[row[i] for row in matrix] for i in range(len(matrix[0]))]. Use zip() unless you need to transform values during transposition.
list(zip(*matrix)) produces a list of tuples: [(1, 5, 9), ...]. If you need lists of lists, wrap it: [list(row) for row in zip(*matrix)].
Two levels is fine. Three or more levels is almost always better written as named loops with comments. The Python docs themselves say to prefer built-in functions over complex flow statements โ the same logic applies to comprehensions.
Python Nested List Comprehension Interview Questions
[[row[i] for row in matrix] for i in range(len(matrix[0]))]. The outer loop iterates over column indices. For each column index i, the inner comprehension picks the i-th element from every row โ which is the i-th column. The result is those columns assembled as rows.* unpacks the matrix into separate row arguments. zip() then pairs up elements by position โ the first element of each row, then the second, and so on. This is exactly matrix transposition. list(zip(*matrix)) returns tuples; wrap with [list(row) for row in zip(*matrix)] if you need lists.[item for row in matrix for item in row]. This is a single list comprehension with two for clauses โ not a nested comprehension. The outer loop iterates over rows, the inner loop over each item in that row. For deeply nested structures, itertools.chain.from_iterable() is more efficient.zip() for pure transposition โ it's faster, shorter, and more readable. Use a nested comprehension when you need to filter or transform values as you build the transposed structure, not just rearrange them.Python Nested List Comprehensions FAQ
[[inner_expr for item in inner_iter] for item in outer_iter]. From the Python docs ยง5.1.4: the inner comprehension is evaluated in the context of the outer for loop.for), then the inner expression. In [[row[i] for row in matrix] for i in range(4)]: first i takes values 0,1,2,3. For each i, the inner comprehension [row[i] for row in matrix] runs completely and produces one row of the result.[[row[i] for row in matrix] for i in range(4)], or list(zip(*matrix)) (returns tuples), or [list(row) for row in zip(*matrix)] (returns lists). The zip() approach is preferred for pure transposition.* is the unpacking operator. It expands the list into separate positional arguments. So zip(*matrix) is equivalent to zip(matrix[0], matrix[1], matrix[2]) โ passing each row as a separate argument to zip(). See the Python docs link to Unpacking Argument Lists for more detail.[[x for x in row] for row in matrix] creates a list of lists (2D). A comprehension with two for clauses [x for row in matrix for x in row] creates a flat list (1D). Same data, different shape.List Comprehension (5.1.3) ยท Lists (5.1) ยท Tuples (5.3) ยท Dictionaries (5.5) ยท Sets (5.4) ยท Lists as Stack and Queue ยท For Loop ยท If Statements ยท User Input ยท While Loop ยท