Can you write this from memory?
Create a dict mapping numbers 1-3 to their squares using a dict comprehension
Python comprehensions look easy in tutorials. Then you're typing one live and blank on where the if goes, or you nest two for clauses and flip the order.
The patterns aren't complicated, but they need to be automatic. These exercises drill the syntax until you stop second-guessing yourself.
The four types:
[expr for x in xs]→ list (like map)[expr for x in xs if cond]→ filtered list{k: v for ...}/{expr for ...}→ dict / set(expr for ...)→ generator (lazy)
| Pattern | What it builds | Example |
|---|---|---|
[expr for x in xs] | list | [x**2 for x in range(5)] → [0, 1, 4, 9, 16] |
[expr for x in xs if cond] | filtered list | [x for x in nums if x > 0] |
{k: v for x in xs} | dict | {w: len(w) for w in words} |
{expr for x in xs} | set | {x.lower() for x in names} |
(expr for x in xs) | generator (lazy) | sum(x*x for x in nums) |
For a printable quick reference, see the Python list comprehensions cheat sheet.
The filter if comes after the iterable:
[expression for variable in iterable if condition]
Think of it as "for each x in xs, if condition is true, include it."
# Only include positives (others are excluded)
positives = [x for x in nums if x > 0]
# Multiple conditions (same as "and")
valid = [x for x in items if x > 0 if x < 100]
# Equivalent to: if x > 0 and x < 100
The if-else goes before the for—it's part of the expression, not a filter:
[value_if_true if condition else value_if_false for variable in iterable]
This transforms every item—nothing is excluded.
# Transform all: positive stays, negative becomes 0
clamped = [x if x > 0 else 0 for x in nums]
# Classify each item
labels = ["even" if x % 2 == 0 else "odd" for x in nums]
Key difference:
- Filter
if(at end): excludes items → shorter list - Ternary
if-else(at start): transforms all → same length
The for-clauses read in the same order as nested loops.
This comprehension:
flat = [x for row in matrix for x in row]
Is equivalent to:
flat = []
for row in matrix: # leftmost = outer
for x in row: # next = inner
flat.append(x)
Memory trick: Read left-to-right. The first for is the outermost loop. If the nesting maps exactly to a standard loop pattern, the comprehension version reads in the same order.
Common pattern: flatten a 2D list
matrix = [[1, 2], [3, 4], [5, 6]]
flat = [n for row in matrix for n in row]
# → [1, 2, 3, 4, 5, 6]
Dict comprehension uses {key: value for ...}:
# Word → length mapping
word_lengths = {word: len(word) for word in words}
# Invert a dict (swap keys and values)
inverted = {v: k for k, v in original.items()}
# Filter a dict
expensive = {k: v for k, v in prices.items() if v > 100}
Set comprehension uses {expr for ...} (no colon). Sets are ideal when you need uniqueness — see the collections guide for when to choose sets over lists:
# Unique lowercase names
unique_names = {name.lower() for name in names}
# Unique first characters
initials = {word[0] for word in words if word}
A generator expression looks like a list comprehension with parentheses:
# List comprehension: builds entire list in memory
squares_list = [x**2 for x in range(1_000_000)]
# Generator expression: lazy, one value at a time
squares_gen = (x**2 for x in range(1_000_000))
When to use generators:
- You only iterate once
- The data is large and you don't need it all in memory
- You're passing directly to a function like
sum(),any(),all()
# No intermediate list allocated
total = sum(x**2 for x in nums)
has_negative = any(x < 0 for x in nums)
all_positive = all(x > 0 for x in nums)
The gotcha: leftmost iterable is evaluated immediately
def get_items():
print("Fetching items...")
return [1, 2, 3]
# This prints "Fetching items..." RIGHT NOW, not when you iterate
gen = (x**2 for x in get_items())
The generator is lazy for producing values, but the source iterable is evaluated when you create the generator.
In Python 3, comprehensions have their own scope:
x = "original"
result = [x for x in range(3)]
print(x) # Still "original"—x wasn't overwritten
This is different from regular for loops, where the variable persists:
x = "original"
for x in range(3):
pass
print(x) # 2—the loop variable leaked
A common inefficiency is calling an expensive function twice:
# BAD: calls process() twice per item
results = [process(x) for x in data if process(x) > threshold]
The walrus operator (Python 3.8+) lets you assign and test in one step:
# GOOD: calls process() once, reuses the result
results = [y for x in data if (y := process(x)) > threshold]
This is especially useful when the filter condition uses the same computed value you want in the output.
Example 1: Map (transform each item)
Loop version:
squares = []
for x in range(10):
squares.append(x ** 2)
Comprehension:
squares = [x ** 2 for x in range(10)]
Example 2: Filter (keep some items)
Loop version:
evens = []
for x in nums:
if x % 2 == 0:
evens.append(x)
Comprehension:
evens = [x for x in nums if x % 2 == 0]
Example 3: Filter + transform
Loop version:
squared_evens = []
for x in nums:
if x % 2 == 0:
squared_evens.append(x ** 2)
Comprehension:
squared_evens = [x ** 2 for x in nums if x % 2 == 0]
Example 4: Nested loops (flatten)
Loop version:
flat = []
for row in matrix:
for item in row:
flat.append(item)
Comprehension:
flat = [item for row in matrix for item in row]
Use a regular loop when:
-
You need side effects (printing, logging, mutating external state)
# Bad: comprehension for side effects [print(x) for x in items] # returns [None, None, ...] # Good: just use a loop for x in items: print(x) -
It's getting hard to read (2+ for-clauses + 2+ conditions)
# Hard to parse result = [f(x, y) for x in xs if p(x) for y in ys if q(x, y)] # Clearer as a loop or helper function -
You need early exit (break/continue logic)
-
The expression is complex enough to deserve a name
When to Use Python Comprehensions: List, Dict, Set & Generator Expressions
- Transform or filter an iterable into a new list/set/dict in one readable expression.
- Replace short "accumulator loops" whose intent is purely map/filter.
- Use a **generator expression** when you only need to iterate once and don't want to allocate a whole list.
Check Your Understanding: Python Comprehensions: List, Dict, Set & Generator Expressions
Given a list of orders, return the ids of orders over $100.
Use a filter + projection: `[o.id for o in orders if o.total > 100]`. It's clearer than a manual accumulator when the intent is "filter then map".
What You'll Practice: Python Comprehensions: List, Dict, Set & Generator Expressions
Common Python Comprehensions: List, Dict, Set & Generator Expressions Pitfalls
- Flipping the order in nested comprehensions—for-clauses match nested loop order (leftmost is outer)
- Forgetting brackets vs parentheses: `[...]` builds a list, `(...)` is a generator
- Trying to do side effects in a comprehension—use a loop for mutations
- Missing parentheses when the expression is a tuple: `[(x, y) for ...]` not `[x, y for ...]`
- Assuming the iteration variable leaks into outer scope (it doesn't since Python 3)
- Lambda closure trap: `[lambda: x for x in range(3)]` all return 2—use `lambda x=x: x` to capture
- Confusing filter `if` (at end, excludes items) with ternary `if-else` (at start, transforms all)
Python Comprehensions: List, Dict, Set & Generator Expressions FAQ
Where does the if go in a list comprehension?
For **filtering**, the `if` comes after the iterable: `[expr for x in xs if cond]`. For **if-else transformation**, put it before: `[a if cond else b for x in xs]`. Filtering excludes items; if-else transforms all items.
What order do nested for-clauses run in?
Same as nested loops: the **leftmost** `for` is the outer loop, the next `for` is inside it. `[x for row in matrix for x in row]` is equivalent to nested `for row in matrix: for x in row:`.
Why isn't my loop variable available after the comprehension?
Comprehensions execute in an implicitly nested scope (since Python 3), so the iteration variable doesn't "leak" into the surrounding scope. This is intentional—it prevents accidental variable shadowing.
Generator expression vs list comprehension?
A list comprehension builds the whole list immediately. A generator expression is **lazy**: it produces values as you iterate. One gotcha: the leftmost iterable is evaluated immediately when the generator is created, so errors can happen at definition time.
Are comprehensions faster than for loops?
Yes, typically 10–30% faster for simple transformations. Comprehensions compile to a dedicated LIST_APPEND bytecode instruction that avoids the per-iteration attribute lookup and method call overhead of .append(). But treat performance as a bonus—readability and correctness come first. Measure if it matters.
When should I avoid a comprehension?
When it has 2+ `for` clauses + 2+ conditions, consider a loop or helper function. If you need side effects (printing, mutating external state), don't use a comprehension—they're for building containers, not running actions.
Can I use multiple if conditions?
Yes: `[x for x in xs if cond1 if cond2]` is valid—it means `cond1 and cond2`. Most prefer the explicit `and` for clarity, but you'll see chained `if`s in legacy code.
What is a dict comprehension in Python?
A dict comprehension builds a dictionary in one expression: `{key: value for item in iterable}`. It's the dict equivalent of a list comprehension. Example: `{word: len(word) for word in words}` creates a word-to-length mapping.
How do you create a map comprehension in Python?
Python doesn't have a literal "map comprehension." The term usually means a dict comprehension (`{k: v for ...}`) that maps keys to values, or using the built-in `map()` function. For a dictionary, use `{k: transform(k) for k in keys}`.
Python Comprehensions: List, Dict, Set & Generator Expressions Syntax Quick Reference
squares = [x**2 for x in range(10)]evens = [x for x in nums if x % 2 == 0]flat = [n for row in matrix for n in row]lengths = {word: len(word) for word in words}unique = {name.lower() for name in names if name}total = sum(x*x for x in nums) # no list allocatedlabels = ["even" if x % 2 == 0 else "odd" for x in nums]pairs = [(i, x) for i, x in enumerate(items)]results = [y for x in data if (y := expensive(x)) > 0]valid = [x for x in items if x > 0 if x < 100] # same as: andPython Comprehensions: List, Dict, Set & Generator Expressions Sample Exercises
What does this dict comprehension create?
{1: 2, 2: 4, 3: 6}Create a dict mapping numbers 1-5 to their squares, but only for even numbers
{n: n**2 for n in range(1, 6) if n % 2 == 0}Create a new dict by swapping keys and values from `item_map`
{v: k for k, v in item_map.items()}+ 34 more exercises
Copy-ready syntax examples for quick lookup
Further Reading
- GDScript Dictionary map() and map_in_place12 min read