When I Stop Reaching for List Comprehensions

I love comprehensions, but I have learned the three cases where they cost more than they save: nested filtering, side effects, and big intermediate lists. Here is the pattern I switch to in each.

Python
Compiler
3 snippets
py-list-comprehensions
py-generators
performance
ayomidegray

By @ayomidegray

February 11, 2026

·

Updated May 18, 2026

628 views

4

3.9 (9)

from __future__ import annotations

# A comprehension I have actually shipped (and regretted):
# Rebuild a routing table by user permission level. Each row has nested data.

rows = [
    {'user': 'alice', 'roles': ['admin', 'editor'], 'features': ['beta']},
    {'user': 'bob',   'roles': ['viewer'],          'features': []},
    {'user': 'carol', 'roles': ['editor'],          'features': ['beta', 'analytics']},
]

# DON'T: the conditions stack up and the predicate becomes unreadable.
bad = [r['user'] for r in rows if 'admin' in r['roles'] or ('editor' in r['roles'] and 'beta' in r['features'])]
print('bad:', bad)

# DO: name the predicate. The comprehension stays one line, the rule is testable.
def can_access_beta(row):
    if 'admin' in row['roles']:
        return True
    if 'editor' in row['roles'] and 'beta' in row['features']:
        return True
    return False

good = [r['user'] for r in rows if can_access_beta(r)]
print('good:', good)

The comprehension is not the problem; the predicate is. The moment a filter has more than one boolean operator I extract it into a named function whose name reads like business logic (can_access_beta). The win is double: the comprehension reverts to one obvious line (pluck users where can_access_beta), and the rule itself is now unit-testable in isolation. I have caught real bugs in this exact extraction step because seeing or ('editor' in r['roles'] and 'beta' in r['features']) written as a function exposes the precedence the inline version is hiding.