List comprehensions are Python's most elegant feature for building lists from iterables. They replace five lines of a for loop with one expressive line — and run faster too.
Quick Reference
| Pattern | Syntax |
|---|---|
| Basic | [expr for item in iterable] |
| With filter | [expr for item in iterable if condition] |
| if/else inline | [a if condition else b for item in iterable] |
| Nested loops | [expr for x in xs for y in ys] |
| Dict comprehension | {k: v for k, v in pairs} |
| Set comprehension | {expr for item in iterable} |
| Generator expression | (expr for item in iterable) |
Basic Syntax
The form is: [expression for variable in iterable].
# Loop version
squares = []
for n in range(1, 6):
squares.append(n ** 2)
# [1, 4, 9, 16, 25]
# Comprehension version
squares = [n ** 2 for n in range(1, 6)]
# [1, 4, 9, 16, 25]
Read it left-to-right: "give me n**2 for each n in range(1, 6)".
Filtering with if
Add an if clause at the end to keep only items that match a condition.
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Keep only even numbers
evens = [n for n in numbers if n % 2 == 0]
# [2, 4, 6, 8, 10]
# Keep words longer than 3 characters
words = ["hi", "hello", "hey", "world", "no"]
long_words = [w for w in words if len(w) > 3]
# ['hello', 'world']
# Filter and transform at once
even_squares = [n ** 2 for n in numbers if n % 2 == 0]
# [4, 16, 36, 64, 100]
Inline if/else (Ternary)
When you want to transform — not filter — use if/else inside the expression (before for):
numbers = [1, 2, 3, 4, 5]
# Label each number
labels = ["even" if n % 2 == 0 else "odd" for n in numbers]
# ['odd', 'even', 'odd', 'even', 'odd']
# Clamp values to a maximum
data = [10, 200, 30, 500, 50]
clamped = [x if x <= 100 else 100 for x in data]
# [10, 100, 30, 100, 50]
Position matters:
[expr_if_true if cond else expr_if_false for item in iterable]— transform every item[expr for item in iterable if cond]— skip items that fail the condition
Calling Functions
The expression can be any callable:
names = [" alice ", "BOB", " Charlie "]
# Strip whitespace and lowercase
cleaned = [name.strip().lower() for name in names]
# ['alice', 'bob', 'charlie']
# Apply a function
import math
roots = [math.sqrt(n) for n in [4, 9, 16, 25]]
# [2.0, 3.0, 4.0, 5.0]
# Call a custom function
def score_to_grade(score):
if score >= 90: return "A"
if score >= 80: return "B"
if score >= 70: return "C"
return "F"
scores = [95, 82, 67, 74]
grades = [score_to_grade(s) for s in scores]
# ['A', 'B', 'F', 'C']
Nested Loops
Two for clauses flatten nested structures:
# Cartesian product
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 = [n for row in matrix for n in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# With a filter on the inner loop
filtered = [n for row in matrix for n in row if n % 2 == 1]
# [1, 3, 5, 7, 9]
The order matches how you'd write nested for loops: outer loop first, inner loop second.
# Equivalent loop
for row in matrix:
for n in row:
if n % 2 == 1:
result.append(n)
Avoid going beyond two levels of nesting — beyond that, a regular loop is easier to read.
Dict Comprehensions
Same idea, but produces a dict:
# Square each number, keyed by the number
{n: n**2 for n in range(1, 6)}
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# 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'}
# Filter a dict — keep only high scorers
scores = {"alice": 90, "bob": 55, "carol": 88, "dave": 40}
passing = {name: score for name, score in scores.items() if score >= 70}
# {'alice': 90, 'carol': 88}
# Normalise keys from a raw API response
raw = {"First Name": "Alice", "Last Name": "Smith"}
normalised = {k.lower().replace(" ", "_"): v for k, v in raw.items()}
# {'first_name': 'Alice', 'last_name': 'Smith'}
Set Comprehensions
Use {} (curly braces, no colon) to produce a set — automatically deduplicates:
words = ["apple", "banana", "apricot", "blueberry", "avocado"]
# Unique first letters
first_letters = {w[0] for w in words}
# {'a', 'b'}
# Deduplicate while transforming
numbers = [1, 2, 2, 3, 3, 3, 4]
unique_squares = {n**2 for n in numbers}
# {1, 4, 9, 16}
Generator Expressions
Swap [] for () to get a lazy generator that doesn't build the list in memory:
# Sum without building the full list
total = sum(n**2 for n in range(1_000_000))
# Any/all use short-circuit evaluation with generators
has_even = any(n % 2 == 0 for n in [1, 3, 5, 4, 7])
# True — stops at 4
all_positive = all(n > 0 for n in [1, 2, -1, 4])
# False — stops at -1
# Pass directly to a function — only one pair of parens needed
print(", ".join(str(n) for n in [1, 2, 3]))
# '1, 2, 3'
Use a generator expression when you only need to iterate once or when the dataset is large.
Real-World Patterns
Parse a CSV row
line = "42,hello,3.14,True"
values = [token.strip() for token in line.split(",")]
# ['42', 'hello', '3.14', 'True']
Extract nested values from JSON
users = [
{"name": "Alice", "active": True},
{"name": "Bob", "active": False},
{"name": "Carol", "active": True},
]
active_names = [u["name"] for u in users if u["active"]]
# ['Alice', 'Carol']
Build a lookup table
words = ["hello", "world", "python"]
length_map = {w: len(w) for w in words}
# {'hello': 5, 'world': 5, 'python': 6}
Transpose a matrix
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
transposed = [[row[i] for row in matrix] for i in range(3)]
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Remove duplicates while preserving order
seen = set()
deduped = [x for x in items if not (x in seen or seen.add(x))]
Comprehension vs Loop: When to Use Each
| Use comprehension | Use a regular loop |
|---|---|
| Single transformation | Multiple statements per iteration |
| Result fits in memory | Very large datasets (use generator) |
| Clear and readable | Complex nested logic |
| Building a list/dict/set | Side effects only (print, file write) |
| Performance matters | Readability is more important |
The rule of thumb: if it doesn't fit on one readable line, write a loop.
# Good — simple and clear
doubles = [x * 2 for x in data]
# Borderline — consider a loop
result = [transform(x) for x in data if predicate(x) and not exclude(x)]
# Too complex — write a loop
result = []
for x in data:
if predicate(x):
y = step_one(x)
z = step_two(y)
result.append(z)
Performance
List comprehensions are typically 20–35% faster than equivalent for loops because the loop overhead runs in C rather than Python bytecode.
import timeit
# Loop
def with_loop():
result = []
for n in range(1000):
result.append(n * 2)
return result
# Comprehension
def with_comp():
return [n * 2 for n in range(1000)]
# timeit shows comprehension wins by ~25%
For very large data you don't need all at once, generator expressions beat both:
# Reads file lazily — constant memory
total = sum(len(line) for line in open("big.txt"))
6 Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
[x for x in range(10) if x else 0] |
if at the end filters, doesn't substitute |
Use x if x else 0 for x in range(10) |
| Nested comprehension for simple flatten | Hard to read | Use itertools.chain.from_iterable |
| Building a huge list only to iterate it | Wastes memory | Use a generator expression |
[d.update({"k": v}) for d in dicts] |
Side effects in comprehensions | Use a regular loop |
[[]] * 3 instead of [[] for _ in range(3)] |
All rows share the same object | Always use comprehension for mutable defaults |
[... for _ in range(n)] when n is large |
_ is fine for the variable, but the list may be huge |
Consider sum(), any(), all(), or a generator |
FAQ
Are list comprehensions always faster than loops?
Usually yes for simple transformations, but profile before optimising. map() with a built-in function (e.g., map(str, nums)) can be even faster because it avoids Python-level function calls entirely.
When should I use map() instead?
When applying a single built-in function: list(map(int, strings)) is idiomatic and fast. For custom lambdas, a comprehension is more readable: [int(s) for s in strings].
Can I use walrus operator (:=) in comprehensions?
Yes, in Python 3.8+. Useful to compute a value once and filter on it:
results = [y for x in data if (y := transform(x)) is not None]
What's the difference between [x for x in lst] and lst.copy()?
Both create a shallow copy, but lst.copy() (or lst[:]) is faster and more explicit when copying is the goal.
Can comprehensions replace filter() and map()?
Yes — [f(x) for x in xs] replaces list(map(f, xs)) and [x for x in xs if p(x)] replaces list(filter(p, xs)). Most Pythonistas prefer comprehensions for readability.
How do I debug a comprehension?
Extract it into a loop temporarily, add print() statements, then convert back once it works.