For Loops vs List Comprehensions
When to reach for a list comprehension and when a plain for loop is the honest choice. A decisive read on readability, performance, and the line where clever becomes unreadable.
The short answer
List Comprehensions over For Loops for most cases. When the job is "transform a sequence into a list," comprehensions are faster, shorter, and signal intent in one line.
- Pick For Loops if producing side effects (writing files, mutating shared state, logging), need early breaks/continues with complex conditions, or the body spans multiple statements that won't fit one expression
- Pick List Comprehensions if building a new list (or set/dict) by transforming and filtering an iterable, and the logic fits cleanly on one line
- Also consider: A generator expression when you don't need the whole list materialized — same syntax, lazy evaluation, no memory blowup on large inputs.
— Nice Pick, opinionated tool recommendations
The Verdict
List comprehensions win the default case and it isn't close. "Build a list from an iterable" is the single most common loop you will ever write, and the comprehension says exactly that in one line: [f(x) for x in xs if cond(x)]. The for-loop equivalent is three lines of ceremony — initialize the accumulator, loop, append — where two of those lines carry zero information. Comprehensions are also measurably faster in CPython because the append happens in C rather than via a bytecode method lookup each iteration. The for loop earns its keep the moment you need side effects, real branching, or a body that won't compress into an expression. But that's the exception, not the rule. Default to the comprehension; downgrade to a loop only when the comprehension stops being readable. Anyone who reaches for result = []; for ...; result.append() to transform a list is just writing assembly for a problem that has a one-liner.
Readability is the real fight
This is where people get religious and most of them are wrong in both directions. A comprehension that filters and maps over one iterable is more readable than the loop — your eye reads "new list of f(x) where cond" as a single thought. But comprehensions have a hard ceiling. The instant you nest two for clauses with an if, or wrap a ternary inside the expression, you've built a write-only line that the next person has to mentally unparse. That's the failure mode comprehension-lovers refuse to admit. The honest rule: if it doesn't fit on one line you can read aloud, it's a for loop now. Don't be clever. A nested comprehension that needs a comment is a loop wearing a costume. For loops scale gracefully to messy logic; comprehensions stay elegant only while the logic stays trivial. Know which side of that line you're on.
Performance, honestly
Comprehensions are faster, but not by enough to justify rewriting anything for speed alone. In CPython the comprehension avoids the repeated list.append attribute lookup and method-call overhead, so you'll see roughly 20–40% speedups on tight transform loops. Real, measurable, and almost never your bottleneck — if your loop is the hot path, the answer is usually NumPy, a vectorized library, or a different algorithm, not micro-optimizing the loop syntax. The genuinely important performance point is memory: a list comprehension materializes the entire result at once. Feed it a million-row file and you hold a million-element list in RAM. That's where the generator expression — identical syntax with parentheses — quietly wins, streaming one item at a time. So: comprehension for speed on small-to-medium lists, generator for large streams, for loop when you weren't going to keep the list anyway. Don't pick syntax for speed; pick it for memory shape.
Side effects belong in loops
Here is the line nobody should cross: never use a comprehension for side effects. [print(x) for x in xs] and [db.save(x) for x in xs] are abuse — you're building a throwaway list of Nones purely to drive a loop, allocating memory for output you discard. It runs, which is exactly why people do it, and it's wrong every time. A comprehension's contract is "I produce a value." When the point is the doing — writing rows, sending requests, mutating state, logging — the for loop is not a fallback, it's the correct tool, and it announces intent honestly to anyone reading. The split is clean: comprehensions answer "what list do I want?", for loops answer "what should happen?". Mixing them up is how you get code review comments. If your comprehension's result is immediately thrown away, you wanted a for loop and you knew it.
Quick Comparison
| Factor | For Loops | List Comprehensions |
|---|---|---|
| Conciseness for transforms | 3 lines: init, loop, append | One expressive line |
| Speed on tight transform loops | Slower — per-iteration append method lookup | ~20-40% faster in CPython |
| Side effects (I/O, mutation, logging) | Correct, honest, idiomatic | Abuse — builds a throwaway list of Nones |
| Complex branching / multi-statement bodies | Scales cleanly, stays readable | Becomes a write-only one-liner fast |
| Large / streaming inputs | Can process lazily without holding a list | Materializes whole list (use a generator expr instead) |
The Verdict
Use For Loops if: You're producing side effects (writing files, mutating shared state, logging), need early breaks/continues with complex conditions, or the body spans multiple statements that won't fit one expression.
Use List Comprehensions if: You're building a new list (or set/dict) by transforming and filtering an iterable, and the logic fits cleanly on one line.
Consider: A generator expression when you don't need the whole list materialized — same syntax, lazy evaluation, no memory blowup on large inputs.
For Loops vs List Comprehensions: FAQ
Is For Loops or List Comprehensions better?
List Comprehensions is the Nice Pick. When the job is "transform a sequence into a list," comprehensions are faster, shorter, and signal intent in one line. The for loop wins for side effects and branching logic, but the default case — build a list from an iterable — belongs to the comprehension. Reach for a loop only when the comprehension stops fitting on one readable line.
When should you use For Loops?
You're producing side effects (writing files, mutating shared state, logging), need early breaks/continues with complex conditions, or the body spans multiple statements that won't fit one expression.
When should you use List Comprehensions?
You're building a new list (or set/dict) by transforming and filtering an iterable, and the logic fits cleanly on one line.
What's the main difference between For Loops and List Comprehensions?
When to reach for a list comprehension and when a plain for loop is the honest choice. A decisive read on readability, performance, and the line where clever becomes unreadable.
How do For Loops and List Comprehensions compare on conciseness for transforms?
For Loops: 3 lines: init, loop, append. List Comprehensions: One expressive line. List Comprehensions wins here.
Are there alternatives to consider beyond For Loops and List Comprehensions?
A generator expression when you don't need the whole list materialized — same syntax, lazy evaluation, no memory blowup on large inputs.
When the job is "transform a sequence into a list," comprehensions are faster, shorter, and signal intent in one line. The for loop wins for side effects and branching logic, but the default case — build a list from an iterable — belongs to the comprehension. Reach for a loop only when the comprehension stops fitting on one readable line.
Related Comparisons
Disagree? nice@nicepick.dev