Why is a comprehension faster than the same loop?

Yes, by a little, and for a specific reason: the loop looks up out.append and calls it on every iteration, while the comprehension appends with a single dedicated opcode, LIST_APPEND. It is a constant factor, not a change in complexity — both are O(n).

Python semanticsConceptualEasy

Step through it

What to watch

  • The loop's per-iteration bytecode contains LOAD_ATTR and CALL; the comprehension's does not.
  • Both forms are O(n) — the difference is per-item overhead.
  • The measured gap is modest, which is the honest answer to the question.

Say this out loud

"A bit faster. The loop does an attribute lookup and a function call per item; the comprehension uses a dedicated LIST_APPEND opcode with neither. It is a constant factor, so I would choose between them on readability, and reach for a generator expression when I do not need the list in memory - which saves the memory, not the time."

Overview

A list comprehension usually runs faster than an equivalent Python loop because iteration and append work happen in optimized interpreter machinery. Prefer it when it remains clear; readability matters more than a small speedup.

Run it

Best-of-five timings, because these are noisy, and then the per-iteration bytecode of each form so the cause is visible rather than asserted.

Best of five runs, because a single measurement here is a coin flip.

1Python
Output

And the reason, which is visible rather than asserted.

2Python
Output

And the version that stores nothing - note what it does and does not buy.

3Python
Output

The difference, in bytecode

The loop performs an append call for each value. In CPython, the comprehension uses a specialised list-building path. The dis output above lets you inspect the exact bytecode for the interpreter running this page; those details can change between versions.

Why the number is smaller than people expect

Folklore puts comprehensions at two or three times faster. And that has not been true for a while. Two things narrowed it.

The specialising interpreter added in 3.11 rewrites hot instruction sequences in place. And a repeated attribute-lookup-then-call on the same type is exactly the pattern it specialises. So the loop's overhead shrank.

And in 3.12 comprehensions were inlined (PEP 709). Before that, a list comprehension created and called a hidden function object on every evaluation. Real overhead that partly cancelled the LIST_APPEND saving. Removing it made comprehensions faster in a way that has nothing to do with the append path.

So the answer to give is "a bit faster because of a per-iteration lookup and call", not a multiplier. The editor below measures it on whichever interpreter you are running.

When the loop is the right answer anyway

A comprehension has to be one expression. The moment the body needs a statement — a try, a break, a log line, two things per item — the loop is the only option, and forcing a comprehension produces the unreadable nested version everybody has met.

Choose for readability. One transformation and one filter often reads well as a comprehension; deeply nested conditions are usually clearer in a loop. And if the result is only going to be consumed once, the better answer is often neither. A generator expression skips building the list at all. That is a memory win rather than a speed one — the editor below measures the two as roughly equal in time — but constant memory against a list of a million items is a larger practical difference than the constant factor this page is about.

The follow-up: what about map and filter

map performs iteration in C but still calls its function once per item. A built-in function such as str can make it competitive; a Python lambda still incurs a Python call per value. Compare both forms on the actual workload.

Edge cases to raise

Volunteering these is most of what separates a correct answer from a good one.

The measurement is noisy. The same code timed twice can differ by half. This is why the editor takes the best of five runs - and why quoting a single-run figure in an interview is a claim you cannot defend.

A comprehension over a huge input still builds the whole list. The speed argument is per item; the memory cost is the whole thing. And for a single pass a generator expression removes it.

Readability wins at the point where you have to re-read it. Two for clauses and a condition is usually the limit, and volunteering that boundary is a better answer than defending comprehensions broadly.

Sources and next steps

How the code works

Best-of-five timings, because these are noisy, and then the per-iteration bytecode of each form so the cause is visible rather than asserted.

How the code works

  1. best_of(fn, n, rounds=5)Timing the same code twice here can differ by 50%, so a single measurement would be a coin flip. Taking the minimum of several runs reports the least-interrupted one.
  2. body(with_loop)The instructions between FOR_ITER and the jump back — that is, one iteration. LOAD_ATTR and CALL are the two the comprehension does not have.
  3. LIST_APPENDOne opcode that appends to the list under construction. There is no name lookup because the list is on the interpreter stack, not in a variable.
  4. sum(i * i for i in range(N))No brackets and no list, so the memory is constant rather than proportional to N. The time is about the same or slightly worse — each item costs a __next__ call, which is roughly what the list build was costing. Reach for it to avoid holding the data, not to go faster.

Change one thing

  • Hoist the lookup out of the loop with append = out.append and re-measure. This is classic advice and the result may surprise you on a modern interpreter — measure rather than assume.
  • Compare map(str, range(N)) against [str(i) for i in range(N)], then against map(lambda i: str(i), ...). The lambda undoes the advantage.

Where this runs

Real CPython, compiled to WebAssembly and running on your own machine — nothing is uploaded. The first run takes a few seconds while the interpreter downloads; after that it is immediate. Need more room, or want to paste your own attempt? Use the Python compiler.

Check yourself

0 of 4

Answer without scrolling back up.

  1. Why is a comprehension faster than an append loop?

  2. How does the speed-up scale with n?

  3. When must you use a loop instead?

  4. If the result is consumed once, a generator expression saves you:

Cheat sheet

Why is a comprehension faster than the same loop?

Time a list comprehension against the equivalent for-loop. Read the bytecode, count the attribute lookups saved, and see where the speedup lives.

INTERVIEW · vizlearn.in/interview/why-a-comprehension-is-faster.html

About the author

Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.