List, Dict & Set Comprehensions
Reviewed & published by Brayan K
Building a new collection from an old one — squaring numbers, filtering out bad rows, flipping a dictionary — is something you'll do dozens of times a day. Comprehensions turn that three-line loop into one expressive, readable line.
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
They're one of the most loved features of Python. Master the pattern once and it works for lists, dicts, sets, and generators alike.
What You'll Learn in This Lesson
- Rewriting a for-loop as a list comprehension
- Filtering with a trailing if condition
- Transforming with a leading if/else expression
- Dict comprehensions for key:value pairs
- Set comprehensions for unique results
- Generator expressions for memory efficiency
1 From Loop to Comprehension
Start with the loop you already know, then watch it collapse into one line. They produce the identical result:
# The familiar way — 3 lines
squares = []
for n in range(1, 6):
squares.append(n ** 2)
print(squares) # [1, 4, 9, 16, 25]
# The comprehension — 1 line
squares = [n ** 2 for n in range(1, 6)]
print(squares) # [1, 4, 9, 16, 25]📐 The anatomy of a comprehension:
[ n ** 2 for n in range(1, 6) ]
^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
what to where it comes from
buildRead it left to right: "Give me n squared, for each n in this range." The expression comes first, the source loop second.
🔍 Filtering with a Trailing if
Add an if at the end to keep only the items you want. This is the filter pattern, and it's incredibly common:
numbers = [4, 7, 10, 13, 16, 21, 24]
# Keep only the even numbers
evens = [n for n in numbers if n % 2 == 0]
print(evens) # [4, 10, 16, 24]
# Filter AND transform in one line
big_doubled = [n * 2 for n in numbers if n > 12]
print(big_doubled) # [26, 32, 42, 48]
# Works on strings too
words = ["apple", "kiwi", "banana", "fig", "cherry"]
long_words = [w.upper() for w in words if len(w) > 4]
print(long_words) # ['APPLE', 'BANANA', 'CHERRY']The trailing if decides whether an item is included. The expression at the front decides what it looks like. You can use either or both.
2 Transforming with a Leading if/else
When you want to change every item based on a condition (not drop any), the conditional expression moves to the front:
temps = [15, 22, 8, 30, 19]
# Label each temperature (keeps ALL items)
labels = ["warm" if t >= 20 else "cool" for t in temps]
print(labels) # ['cool', 'warm', 'cool', 'warm', 'cool']
# Cap values at a maximum
scores = [88, 105, 72, 99, 130]
capped = [s if s <= 100 else 100 for s in scores]
print(capped) # [88, 100, 72, 99, 100]# Process a batch of raw sensor readings
readings = [-2, 14, 99, 7, -5, 23, 0, 41]
# 1. Keep only valid (non-negative) readings
valid = [r for r in readings if r >= 0]
print("Valid:", valid)
# 2. Flag each valid reading as high or normal
flags = ["HIGH" if r > 20 else "ok" for r in valid]
print("Flags:", flags)
# 3. Convert valid readings to a quick percentage of 100
pcts = [round(r / 100, 2) for r in valid]
print("As pct:", pcts)
# ✅ Expected output:
# Valid: [14, 99, 7, 23, 0, 41]
# Flags: ['ok', 'HIGH', 'ok', 'HIGH', 'ok', 'HIGH']
# As pct: [0.14, 0.99, 0.07, 0.23, 0.0, 0.41]3 Dictionary Comprehensions
Same idea, but you produce key: value pairs inside curly braces. Perfect for building lookups or flipping dictionaries:
# Build a lookup of number -> its square
squares = {n: n ** 2 for n in range(1, 6)}
print(squares) # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# Flip a dictionary (swap keys and values)
prices = {"coffee": 3, "tea": 2, "juice": 4}
by_price = {price: name for name, price in prices.items()}
print(by_price) # {3: 'coffee', 2: 'tea', 4: 'juice'}
# Filter a dictionary down to cheap items
cheap = {name: p for name, p in prices.items() if p < 4}
print(cheap) # {'coffee': 3, 'tea': 2}Notice .items() gives you both the key and value to work with — and that pair is unpacked exactly like the tuples from the last lesson.
🧩 Set Comprehensions & Generator Expressions
Set comprehension — unique results automatically
words = ["Hello", "world", "HELLO", "World"]
# Collect the unique lowercase forms (duplicates collapse)
unique = {w.lower() for w in words}
print(unique) # {'hello', 'world'}
# Unique word lengths
lengths = {len(w) for w in words}
print(lengths) # {5}Generator expression — lazy and memory-light
Swap the square brackets for round ones and Python produces items one at a time instead of building the whole list. Great for huge data or feeding a function:
# No giant list in memory — sums on the fly
total = sum(n ** 2 for n in range(1, 1000001))
print(total) # 333333833333500000
# any() / all() short-circuit beautifully with generators
nums = [4, 8, 15, 16, 23]
print(any(n > 20 for n in nums)) # True
print(all(n > 0 for n in nums)) # True🧩 Reorder Challenge
These lines should build a dictionary of passing students and print how many there are. Reorder them so the output is Passing: 2.
A) passing = {n: s for n, s in scores.items() if s >= 60}
B) scores = {"Ada": 88, "Ben": 42, "Cleo": 71}
C) print(f"Passing: {len(passing)}")
D) PASS = 60D → B → A → C
PASS = 60 # D (documents the threshold)
scores = {"Ada": 88, "Ben": 42, "Cleo": 71} # B
passing = {n: s for n, s in scores.items() if s >= 60} # A
print(f"Passing: {len(passing)}") # C -> Passing: 2Why: the scores dict (B) must exist before the comprehension (A) can iterate over .items(). Defining PASS (D) first keeps the threshold documented at the top. Only Ada (88) and Cleo (71) clear 60, so the count is 2.
🧠 Quick Recall
print([x * 2 for x in range(4)])[0, 2, 4, 6] — range(4) yields 0,1,2,3 and each is doubled.
2. What is the result?
print([x for x in [1, 2, 3, 4] if x % 2])[1, 3] — x % 2 is truthy (1) for odd numbers and falsy (0) for even, so only odds pass the filter.
3. What does this build?
print({len(w) for w in ["hi", "yo", "hey"]}){2, 3} — a set comprehension collects unique lengths: "hi" and "yo" are 2, "hey" is 3, and the duplicate 2 collapses.
📋 Comprehension Quick Reference
| Type | Brackets | Example |
|---|---|---|
| List | [ ] | [x*x for x in r] |
| Dict | { k: v } | {x: x*x for x in r} |
| Set | { } | {x*x for x in r} |
| Generator | ( ) | (x*x for x in r) |
| Filter | trailing if | [x for x in r if x > 0] |
🎯 Mini Challenge: Word Frequencies
Use comprehensions to clean a sentence and build a length lookup.
# 🎯 YOUR TURN: text analysis with comprehensions
sentence = "the quick brown fox jumps over the lazy dog"
words = sentence.split()
# 1. Words longer than 3 letters, uppercased
big = [w.upper() for w in words if len(w) > 3]
print("Big words:", big)
# 2. A dict mapping each word -> its length
lengths = {w: len(w) for w in words}
print("Lengths:", lengths)
# 3. The set of unique word lengths
unique_lengths = {len(w) for w in words}
print("Unique lengths:", unique_lengths)
# ✅ Expected output:
# Big words: ['QUICK', 'BROWN', 'JUMPS', 'OVER', 'LAZY']
# Lengths: {'the': 3, 'quick': 5, 'brown': 5, 'fox': 3, 'jumps': 5, 'over': 4, 'lazy': 4, 'dog': 3}
# Unique lengths: {3, 4, 5}Lesson complete — you write Pythonic code now!
You can collapse loops into list, dict, and set comprehensions, filter with a trailing if, transform with a leading if/else, and reach for a generator when memory matters. This is some of the most idiomatic Python there is.
Practice quiz
What does [n ** 2 for n in range(1, 6)] produce?
- [1, 4, 9, 16, 25]
- [0, 1, 4, 9, 16]
- [1, 2, 3, 4, 5]
- [2, 4, 6, 8, 10]
Answer: [1, 4, 9, 16, 25]. It squares each n from 1 to 5: [1, 4, 9, 16, 25].
In a comprehension, where does a FILTERING if go?
- At the front
- At the end (trailing)
- It can't be used
- In the middle
Answer: At the end (trailing). A trailing if, e.g. [x for x in items if x > 0], filters which items are kept.
What does [x for x in [1, 2, 3, 4] if x % 2] produce?
- [2, 4]
- [1, 3]
- [1, 2, 3, 4]
- []
Answer: [1, 3]. x % 2 is truthy for odd numbers, so only the odds 1 and 3 pass.
When you need an if/else that transforms EVERY item, where does it go?
- At the end
- At the front (leading)
- It is not allowed
- After the for
Answer: At the front (leading). A leading if/else, e.g. [x if cond else y for x in items], transforms every item without dropping any.
Which brackets build a dictionary comprehension?
- [ ]
- ( )
- { } with key: value
- < >
Answer: { } with key: value. Dict comprehensions use curly braces with key: value pairs, e.g. {k: v for ...}.
What does {n: n ** 2 for n in range(1, 4)} create?
- {1: 1, 2: 4, 3: 9}
- [1, 4, 9]
- {1, 4, 9}
- (1, 4, 9)
Answer: {1: 1, 2: 4, 3: 9}. It maps each n to its square in a dictionary: {1: 1, 2: 4, 3: 9}.
What does a set comprehension like {len(w) for w in ['hi', 'yo', 'hey']} give?
- {2, 2, 3}
- {2, 3}
- [2, 3]
- {5}
Answer: {2, 3}. Set comprehensions keep unique results: lengths 2 (hi, yo) and 3 (hey) collapse to {2, 3}.
Which brackets create a generator expression?
- [ ]
- { }
- ( )
- < >
Answer: ( ). Generator expressions use round parentheses, e.g. (x*x for x in r).
Why use a generator expression over a list comprehension for huge data?
- It is sorted
- It produces items lazily, using little memory
- It is always faster to index
- It removes duplicates
Answer: It produces items lazily, using little memory. A generator yields items one at a time instead of building the whole list, saving memory.
What does [x * 2 for x in range(4)] produce?
- [2, 4, 6, 8]
- [0, 2, 4, 6]
- [0, 1, 2, 3]
- [0, 2, 4, 6, 8]
Answer: [0, 2, 4, 6]. range(4) is 0,1,2,3 and each is doubled: [0, 2, 4, 6].
Continue this course
- Previous: Tuples
- Next: enumerate & zip — Loop with an index, and loop over multiple iterables at once
- Quick reference: Python cheat sheet › Control Flow
Frequently asked questions
What is a list comprehension?
A list comprehension is a compact way to build a list from an existing iterable in a single expression. Instead of writing an empty list plus a for-loop plus append, you write [expression for item in iterable]. It's shorter, often faster, and once you're used to it, easier to read.
When should I NOT use a comprehension?
When it stops being readable. If you need multiple nested conditions, side effects (like printing), several statements, or a try/except, write a normal loop. Comprehensions are for transforming data into a new collection — not for doing complicated work. A line that needs scrolling to read is a code smell.
Where does the if go in a comprehension — and can I use else?
A filtering if goes at the end: [x for x in items if x > 0] keeps only matching items. If you also need an else, it becomes a conditional expression that moves to the front: [x if x > 0 else 0 for x in items]. The trailing if filters; the leading if/else transforms every item.
Can I build dictionaries and sets this way too?
Yes. A dict comprehension uses key: value pairs inside braces: {k: v for ...}. A set comprehension looks the same but with a single expression: {expr for ...}. Both follow the identical pattern as list comprehensions, just with different brackets, so learning one teaches you all three.
What's a generator expression and how is it different?
A generator expression uses round parentheses instead of square brackets and produces items lazily, one at a time, instead of building the whole list in memory. (x*x for x in range(1000000)) uses almost no memory. Use it when you only need to iterate once, especially over large data, or inside functions like sum() and any().