The Basic Form

A list comprehension turns this:

squares = []
for x in range(10): squares.append(x * x)

Into this:

squares = [x * x for x in range(10)]

Same result, one line, slightly faster, more idiomatic. The pattern is [expression for variable in iterable]. Read it left to right: "for each x in range(10), give me x*x".

Filtering with if

Add an if at the end to filter:

# 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 non-empty strings, uppercased
words = ["hello", "", "world", " ", "python"]
clean = [w.upper() for w in words if w.strip()]
# ['HELLO', 'WORLD', 'PYTHON']

Conditional Expression (Different Syntax!)

There's a critical syntactic distinction: filter if goes at the end, but a conditional expression goes in the middle:

# Filter: only include if condition is true
[x for x in nums if x > 0] # Conditional expression: include all, but transform differently
[x if x > 0 else 0 for x in nums] # negative numbers become 0

These read similarly but do completely different things. The first removes items; the second transforms them. Mix them up and your output is wrong in subtle ways.

Dict and Set Comprehensions

# Dict comprehension
squares_dict = {x: x * x for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16} # Build a lookup from a list
users = [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
by_id = {u["id"]: u for u in users} # Set comprehension — duplicates removed automatically
unique_lengths = {len(w) for w in ["hi", "hello", "hey"]}
# {2, 3, 5}

Nested Comprehensions

Useful for flattening or building 2D structures, but readability degrades fast:

# Flatten a 2D list
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [x for row in matrix for x in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9] # Build a 3x3 identity matrix
identity = [[1 if i == j else 0 for j in range(3)] for i in range(3)]

Read nested comprehensions left-to-right exactly like nested for-loops: outer loop first, inner loop next, expression at the start. Past one level of nesting, just write a for-loop — your future self will thank you.

Generator Expressions for Big Data

Swap [] for () and you get a generator — lazy, memory-efficient, perfect for huge sequences:

# List: builds all 10 million squares in memory
total = sum([x * x for x in range(10_000_000)]) # Generator: produces one square at a time
total = sum(x * x for x in range(10_000_000)) # parentheses can be implicit in function call

For aggregations like sum, max, any, generators are almost always better — same result, fraction of the memory.

When to Use a Regular for-loop

Comprehensions are great for transformation. They're bad for:

  • Side effects. Printing, logging, modifying external state — these belong in a for-loop.
  • Multi-line expressions. If your transformation needs 5 lines of helper logic, extract a function and call it from the comprehension or just write a loop.
  • Try/except. Comprehensions can't catch exceptions inline — wrap in a function or use a loop.
  • Anything past one level of nesting. Two nested for clauses is the comfortable maximum.

Real-world Patterns

# Parse CSV-ish data
rows = ["1,Alice,30", "2,Bob,25", "3,Charlie,35"]
records = [{"id": int(p[0]), "name": p[1], "age": int(p[2])} for line in rows for p in [line.split(",")]] # Filter + transform in one pass
adult_names = [u["name"] for u in users if u["age"] >= 18] # Invert a dict
inverted = {v: k for k, v in original.items()} # Group items by a key (use defaultdict for groups)
from collections import defaultdict
groups = defaultdict(list)
for item in items: groups[item.category].append(item)
# Comprehensions can NOT do this cleanly — for-loop wins

Practice It

The fastest way to internalize comprehensions: every time you write a for-loop with.append(), pause and ask "could this be a comprehension?". Half the time it can, and the rewrite is shorter and clearer. Test rewrites instantly in the online Python editor — paste, run, compare.