Community Python Snippet
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.
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.
By @ayomidegray
February 11, 2026
·
Updated May 18, 2026
628 views
4
3.9 (9)
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.
A comprehension that returns [None, None, ...] is a code smell I correct in every code review. The intent is wrong: comprehensions are for building a value, loops are for performing actions. Worse, the throwaway list briefly holds one entry per input, which is fine for 4 requests and a bug for 40 million. The mixed case (build a result AND log) is where teams sometimes try a clever walrus-operator comprehension; resist the urge. A four-line for-loop with two accumulators reads better than any expression you can fit in one line.
The single-character change is [ ... ] to ( ... ), but the consequences are everything: a comprehension is eager and fully materializes, a generator expression is lazy and produces values on demand. Combined with itertools.islice you get early termination for free, so a [:5] slice that would have walked 10 million rows now walks until the fifth match. The rule I follow: if the input could conceivably be larger than memory, default to a generator and convert to a list at the call site that needs random access. The only reason to keep the eager comprehension is if you genuinely need the whole list and benefit from local random access.
