List Comprehensions: Build Lists Without the Loop Noise
- A list comprehension collapses a for loop and optional filter into one expression.
- The
ifclause filters items before the expression acts on them. - Nesting is legal but quickly hurts readability; prefer a plain loop beyond two levels.
- Use a generator expression instead when you only need to iterate, not store results.
Why comprehensions exist
A common pattern in any language is: start with a collection, apply some transformation or filter, and put the results into a new collection. In Python, the naive version looks like this:
numbers = [1, 2, 3, 4, 5, 6]
squares = []
for n in numbers:
squares.append(n * n)
That is four lines and a mutable accumulator variable for what is conceptually a single operation. Python introduced list comprehensions to express this pattern as a single statement that reads almost like English: give me [expression] for each item in collection.
Basic syntax
The bracket syntax mirrors the mathematical set-builder notation:
[expression for item in iterable]
Applied to the squares example:
squares = [n * n for n in numbers]
# [1, 4, 9, 16, 25, 36]
The expression is evaluated once per iteration. The item name is scoped to the comprehension in Python 3, so it does not leak into the surrounding scope. Any iterable works: lists, tuples, strings, ranges, file objects, generator expressions, and anything implementing __iter__.
chars = [c.upper() for c in "hello"]
# ['H', 'E', 'L', 'L', 'O']
pairs = [(x, y) for x in range(3) for y in range(3)]
# all 9 combinations of (0,0) through (2,2)
Filtering with if
An optional if clause placed after the for clause filters which items reach the expression. Items that fail the test are silently dropped:
evens = [n for n in range(20) if n % 2 == 0]
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
long_words = [w for w in sentence.split() if len(w) > 5]
The if here acts as a guard. It is different from a ternary expression in the expression slot, which runs for every item but produces different values:
# ternary in expression slot: runs for all items
labels = ["even" if n % 2 == 0 else "odd" for n in range(6)]
# ['even', 'odd', 'even', 'odd', 'even', 'odd']
# if in filter slot: drops odd numbers entirely
only_evens = [n for n in range(6) if n % 2 == 0]
# [0, 2, 4]
Nested comprehensions
Flattening a 2D list is a classic use case for a nested comprehension:
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [cell for row in matrix for cell in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
Read nested comprehensions left to right: the outermost for is written first, matching the order of a regular nested loop. Avoid going deeper than two levels; beyond that, a conventional loop is almost always easier to follow.
Loop vs comprehension at a glance
| Situation | Loop | Comprehension |
|---|---|---|
| Simple transform | Verbose, fine | Ideal |
| Filter + transform | Clear | Concise |
| Side effects needed | Required | Wrong tool |
| Deep nesting (3+ levels) | Clearer | Hard to read |
| Large dataset, no storage needed | Use generator | Use generator expression |
| Complex logic per item | Preferred | Gets unreadable |
When not to use them
Do not use a comprehension for side effects. If the point of the loop is to call a function for its effect rather than to collect values, a comprehension is the wrong shape. A list comprehension that nobody reads is just wasted memory.
# Wrong: side-effect loop dressed as a comprehension
[print(item) for item in data] # creates a list of Nones
# Correct: just loop
for item in data:
print(item)
Prefer generator expressions when you do not need a list. If the result feeds directly into sum(), any(), max(), or a loop, use parentheses instead of brackets. The generator version never materialises the full list in memory:
total = sum(n * n for n in range(1_000_000)) # no list built
any_long = any(len(w) > 10 for w in words) # short-circuits
Split complex comprehensions into named steps. If the expression or the filter condition requires more than one mental step to parse, assign intermediate results to variables and use a regular loop. The interpreter does not care which form you choose; the person reading the code at 11pm does.
{k: v for k, v in pairs}), or curly braces with a single expression for a set comprehension ({x * 2 for x in items}).