How do comprehensions work, and when do they hurt readability or performance?

6 minintermediatecollectionscomprehensions

Quick Answer

A comprehension (`[expr for x in iterable if cond]`) is syntax sugar for a `for` loop that builds a new list/set/dict, and it's implemented as its own **hidden function scope**, which is usually slightly faster than the equivalent explicit loop due to bytecode-level optimizations. They hurt readability once nested more than one level deep or once the expression/condition is complex — at that point, an explicit loop with named intermediate variables is clearer, not a performance sacrifice.

Detailed Answer

The three comprehension forms

squares = [x * x for x in range(10)]                    # list
evens = {x for x in range(10) if x % 2 == 0}             # set
lookup = {x: x * x for x in range(10)}                   # dict
gen = (x * x for x in range(10))                          # generator expression (lazy!)

Each desugars roughly to a loop that appends/adds/assigns into a new container (except the generator expression, which produces a lazy iterator instead of eagerly building a container — see the iterators/generators topic).

Why they're often faster than an explicit loop

# Comprehension
squares = [x * x for x in range(1000)]

# Equivalent explicit loop
squares = []
for x in range(1000):
    squares.append(x * x)

Both do the same logical work, but the comprehension compiles to specialized bytecode (LIST_APPEND inside an isolated function frame) that avoids repeated attribute lookups (squares.append) on every iteration — in practice this is a modest, not dramatic, speedup, so "use comprehensions for performance" is a secondary benefit, not the main reason to reach for them.

When they hurt readability

# Hard to read -- nested comprehension with a condition
result = [item.strip().lower() for sublist in data
          for item in sublist if item and not item.startswith("#")]

# Clearer as an explicit loop
result = []
for sublist in data:
    for item in sublist:
        if item and not item.startswith("#"):
            result.append(item.strip().lower())

A comprehension nested two or more levels deep, or one combining multiple if conditions and a non-trivial expression, usually reads worse than the equivalent loop — there's no room for named intermediate variables or comments explaining a non-obvious filter, and reviewers have to mentally un-nest the comprehension to understand execution order (which, notably, goes left-to-right the way the for/if clauses are written, not inside- out).

A good rule of thumb

If a comprehension needs more than one for clause or more than one condition to express the logic, or if the transformation expression itself needs a helper function to stay readable, switch to an explicit loop (or a generator function with clear intermediate steps).

Interview-ready summary: Comprehensions are syntax sugar for building a list/set/dict via a loop, and are typically a bit faster due to specialized bytecode — but that's secondary to their real value: concise, readable code for a simple transform-and-filter. Once nesting or conditions pile up, prefer an explicit loop over a comprehension that's technically correct but hard to read.