:::note[TL;DR]
- Syntax:
[expression for item in iterable]— replaces loops that append to a list - Filter with
ifat the end:[x for x in data if x > 0] - Conditional transform with ternary before the
for:[x if x > 0 else 0 for x in data] - Dict/set comprehensions use
{}with the same pattern; generator expressions use()for lazy evaluation - Don’t use comprehensions for side effects (
print, mutations) or complex multi-condition logic — use a regular loop :::
A list comprehension is a compact way to create a list in Python. Instead of writing a loop that appends to a list, you write the whole thing in one line. It runs faster, it’s more readable when done right, and it’s considered idiomatic Python.
Here’s the difference. Old way:
squares = []
for x in range(10):
squares.append(x ** 2)
With a list comprehension:
squares = [x ** 2 for x in range(10)]
Same result. One line. No temporary variable, no append.
How the syntax works
[expression for item in iterable]
expression— what you want in the new list (can be any expression usingitem)item— the loop variableiterable— anything you can loop over: a list, range, string, dict, etc.
# Double every number
doubled = [x * 2 for x in [1, 2, 3, 4, 5]]
# [2, 4, 6, 8, 10]
# Get length of each word
lengths = [len(word) for word in ["hello", "world", "python"]]
# [5, 5, 6]
# Uppercase every string
upper = [s.upper() for s in ["foo", "bar", "baz"]]
# ["FOO", "BAR", "BAZ"]
Adding a condition (filter)
[expression for item in iterable if condition]
The if clause filters items — only items where the condition is True make it into the result.
# Only even numbers
evens = [x for x in range(20) if x % 2 == 0]
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# Only strings longer than 3 chars
long_words = [w for w in ["hi", "hello", "hey", "howdy"] if len(w) > 3]
# ["hello", "howdy"]
# Remove None values
clean = [x for x in [1, None, 2, None, 3] if x is not None]
# [1, 2, 3]
The scenario: You pull user records from a database and half of them have
Nonefor email. Before sending a bulk email you need only valid addresses. One line:emails = [u.email for u in users if u.email is not None]. Done.
If-else in a comprehension
You can use a ternary expression to transform values conditionally:
# if-else goes in the expression, before the for
result = [x if x > 0 else 0 for x in [-1, 2, -3, 4, -5]]
# [0, 2, 0, 4, 0] — negatives become 0
labels = ["even" if x % 2 == 0 else "odd" for x in range(6)]
# ["even", "odd", "even", "odd", "even", "odd"]
:::warning
The ternary expression must go before the for keyword, not after. [x if x > 5 else 0 for x in range(10)] is valid. [x for x in range(10) if x > 5 else 0] is a syntax error. This is the most common mistake when first writing conditional comprehensions.
:::
Note the position: value_if_true if condition else value_if_false comes before the for. This trips people up.
# WRONG — this is a syntax error
[x for x in range(10) if x > 5 else 0]
# RIGHT — ternary goes in the expression
[x if x > 5 else 0 for x in range(10)]
Nested loops
You can loop over two iterables:
# All combinations of two lists
pairs = [(x, y) for x in [1, 2, 3] for y in ["a", "b"]]
# [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b'), (3, 'a'), (3, 'b')]
# Flatten a 2D list
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [num for row in matrix for num in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
Read the loops in order: outer loop first, inner loop second. Same order as nested for loops.
Dict and set comprehensions
The same syntax works for dicts and sets:
# Dict comprehension
squares_dict = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# Invert a dict (swap keys and values)
original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
# {1: "a", 2: "b", 3: "c"}
# Set comprehension (unique values)
unique_lengths = {len(w) for w in ["hi", "hello", "hey", "howdy", "hi"]}
# {2, 5}
Generator expressions
Replace [] with () and you get a generator instead of a list — lazy evaluation, no memory allocation upfront:
gen = (x**2 for x in range(1_000_000))
next(gen) # 0 — computed on demand
sum(gen) # sums without building a list in memory
Use generators when you’re processing large data and only need to iterate once. Use lists when you need random access or need to use the data multiple times.
:::tip
When passing to sum(), max(), min(), or any function that iterates once, use a generator expression instead of a list comprehension — sum(x**2 for x in range(1_000_000)) uses constant memory while sum([x**2 for x in range(1_000_000)]) allocates a million-element list first.
:::
When NOT to use a list comprehension
List comprehensions are not always the right tool.
When the logic is complex:
# Hard to read — use a regular loop instead
result = [process(x) for x in data if x.status == "active" and x.age > 18 and not x.banned]
# Better
result = []
for x in data:
if x.status == "active" and x.age > 18 and not x.banned:
result.append(process(x))
When you have side effects:
# BAD — comprehensions are for building lists, not for side effects
[print(x) for x in items] # works but wrong tool
# Use a regular loop
for x in items:
print(x)
When you need early exit: List comprehensions always process the entire iterable. If you need to stop early, use a loop with break.
Performance
List comprehensions are faster than equivalent for loops with append — typically 20-30% faster in CPython. The reason: append has overhead per call, and comprehensions are optimized at the bytecode level.
import timeit
# For loop with append
timeit.timeit('[].append(x) or x for x in range(1000)', number=10000)
# List comprehension
timeit.timeit('[x for x in range(1000)]', number=10000)
# Comprehension is consistently faster
The difference is noticeable at scale but rarely the bottleneck in real code. Prefer comprehensions for readability, not just speed.
Related: Python Cheat Sheet | Python Virtual Environments
Summary
- List comprehensions are
[expression for item in iterable]— a faster, more Pythonic alternative to loops withappend - Add a filter with
if conditionat the end; add a ternaryvalue_if_true if cond else value_if_falsein the expression for transformations - Dict and set comprehensions use the same syntax with
{}instead of[] - Generator expressions use
()and are lazy — use them for large data you only iterate once - Don’t use comprehensions for side effects or complex multi-condition logic — a regular loop is cleaner there
Frequently Asked Questions
Are list comprehensions always faster than for loops?
In CPython they’re typically 20–30% faster than equivalent loops with append, because append has per-call overhead and comprehensions are optimized at the bytecode level. The difference rarely matters in real code — choose comprehensions for readability, not micro-optimization.
Can I nest list comprehensions?
Yes. [num for row in matrix for num in row] flattens a 2D list. Read the loops left to right — outer loop first, inner loop second. Avoid more than two levels of nesting; it becomes hard to read quickly.
What’s the difference between a list comprehension and a generator expression?
A list comprehension [x**2 for x in range(n)] builds the entire list in memory immediately. A generator expression (x**2 for x in range(n)) computes values lazily on demand. Use generators when you’re passing results to sum(), max(), or iterating once over large data.
What to Read Next
- Python Cheat Sheet — full Python syntax reference including all comprehension types
- Python Virtual Environments — set up your isolated Python environment before building projects