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 ... 89

When 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