List Comprehension vs Generator in Python
Two of Python’s most elegant features — list comprehensions and generator expressions. Both build sequences from existing iterables, but they behave very differently under the hood.
List Comprehension — Eager
Produces the entire list in memory at once. Use when you need random access, multiple passes, or the result is small.
# Basic
squares = [x**2 for x in range(10)]
# With condition
evens = [x for x in range(20) if x % 2 == 0]
# Nested loops
pairs = [(a, b) for a in range(3) for b in range(3)]
# With if-else (ternary)
labels = ["even" if x % 2 == 0 else "odd" for x in range(10)]Generator Expression — Lazy
Produces values one at a time on iteration. Minimal memory footprint. Use for large data streams, pipelines, or single-pass logic.
# Basic — parentheses instead of brackets
squares = (x**2 for x in range(10))
# Condition
evens = (x for x in range(20) if x % 2 == 0)
# Must be consumed explicitly
for val in squares:
print(val)
# Or unpack
list(evens)
sum(x**2 for x in range(1000))
any(x > 5 for x in range(10))Side-by-Side Comparison
| Aspect | List Comprehension | Generator Expression |
|---|---|---|
| Syntax | [expr for x in it] |
(expr for x in it) |
| Evaluation | Eager (builds all at once) | Lazy (yields one by one) |
| Memory | Stores entire list in RAM | Produces & discards items |
| Reusability | Can iterate multiple times | Exhausted after one pass |
| Speed | Faster for small datasets | Slower per iteration, but constant memory |
| Random Access | result[5] works |
No indexing |
| When to use | Small data, multiple passes, need indexing | Large data, pipelines, single pass |
Memory Example
import sys
lc = [x for x in range(100_000)]
gen = (x for x in range(100_000))
print(sys.getsizeof(lc)) # ~824 KB
print(sys.getsizeof(gen)) # ~112 B (constant regardless of range)The generator consumes the same ~112 bytes whether you iterate over 10 or 10 million items.
Generator Functions (yield)
Beyond generator expressions, you can write generator functions with yield — useful when the logic is too complex for a one-liner.
def fibonacci(limit):
a, b = 0, 1
while a < limit:
yield a
a, b = b, a + b
for n in fibonacci(100):
print(n) # 0, 1, 1, 2, 3, 5, 8 ... 89When You Need Both — Chain With Parentheses
Generator expressions don’t need extra parens when they’re the sole argument to a function:
# No extra parens needed
total = sum(x**2 for x in range(100))
# But explicit parens if not the only argument
total = sum((x**2 for x in range(100)), start=0)Summary
| You want this | Use this |
|---|---|
| Store results for reuse | List comprehension |
| Process a huge file line-by-line | Generator |
Pass to sum, any, all, max, min |
Generator (no extra parens) |
| Index or slice the result | List comprehension |
| Pipeline of transformations | Chained generators |
| Complex multi-step generation | Generator function with yield |