← Back

Home › Python List Comprehension

Python List Comprehension — syntax, examples and the pitfalls

A list comprehension builds a new list from an existing iterable in one expression. It replaces the single most common pattern in beginner Python — make an empty list, loop, append — and it does so faster and without leaking the loop variable into the surrounding scope. The syntax takes a minute; knowing where the construct stops being a good idea is the part worth reading on. Every example below runs in the browser playground.

The syntax

[ expression  for item in iterable  if condition ]
   ^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^   ^^^^^^^^^^^
   what to     where it comes from    optional filter
   store

Read it in that order and it matches English: give me x * 2 for every x in nums where x > 0.

nums = [1, -2, 3, -4, 5]

# the loop it replaces
result = []
for x in nums:
    if x > 0:
        result.append(x * 2)

# the comprehension
result = [x * 2 for x in nums if x > 0]     # [2, 6, 10]

Two details people miss. First, the if at the end filters which items enter the list; an if/else inside the expression transforms every item. They are not interchangeable:

[x for x in nums if x > 0]              # filter — output is shorter
[x if x > 0 else 0 for x in nums]       # transform — output keeps its length

Second, the loop variable does not leak. After a comprehension, x is undefined — unlike a for loop, where it survives with its last value. That is one of the reasons interviewers ask about it.

The forms you will actually use

# transform
[x * 2 for x in nums]
[s.strip().lower() for s in names]
[str(n) for n in ids]                       # numbers to strings for join()

# filter
[x for x in nums if x % 2 == 0]
[line for line in lines if line.strip()]    # drop blank lines
[u for u in users if u["active"]]

# transform and filter together
[u["email"] for u in users if u["active"]]

# with the index
[f"{i}: {name}" for i, name in enumerate(names)]

# from two iterables
[a + b for a, b in zip(one, two)]

# conditional default
[x if x is not None else 0 for x in maybe_none]

# nested data
[row["total"] for row in report["rows"]]
[order for batch in batches for order in batch]   # flatten one level

Nested comprehensions

Extra for clauses go left to right, outer loop first. The expression at the front can use every variable introduced so far:

grid = [[1, 2, 3], [4, 5, 6]]

[x for row in grid for x in row]        # [1, 2, 3, 4, 5, 6] — flattened

# equivalent loop, which reads more clearly once nesting gets deep
flat = []
for row in grid:
    for x in row:
        flat.append(x)

# keep the structure: a comprehension inside a comprehension
[[x * 2 for x in row] for row in grid]  # [[2, 4, 6], [8, 10, 12]]

# a filter on the inner loop
[x for row in grid for x in row if x % 2 == 0]     # [2, 4, 6]

# a multiplication table as nested lists
[[i * j for j in range(1, 4)] for i in range(1, 4)]

The practical limit is two for clauses. With three, or with an if attached to each, the comprehension has to be mentally unwound to be understood at all — and at that point the nested loop is the clearer code. Comprehensions exist to make simple transformations obvious, not to compress arbitrary logic.

Dict, set and generator comprehensions

The same grammar produces the other three built-in collections. Only the brackets change, plus the key/value pair for a dict:

# dict comprehension — note the colon
{u["id"]: u["name"] for u in users}
{name: len(name) for name in names}
{k: v for k, v in pairs if v}                     # filtered
{v: k for k, v in mapping.items()}                # keys and values swapped

# set comprehension — duplicates collapse
{w.lower() for w in words}                        # unique, case-folded
{n % 3 for n in nums}                             # the distinct remainders

# generator expression — parentheses, and it is LAZY
gen = (x * x for x in range(10**9))               # builds nothing yet
next(gen)                                         # 0 — one item at a time

# most useful when consumed immediately
sum(x * x for x in nums)                          # no parentheses needed inside a call
max(len(n) for n in names)
"\n".join(str(r) for r in rows)
any(n < 0 for n in nums)

The generator form is the one to reach for with large data. [x for x in huge] materialises every item in memory before you see the first one; (x for x in huge) holds essentially nothing and works on infinite sequences. If the result is consumed once by sum, max, any or join, the generator is strictly better.

When a plain loop is the better answer

A comprehension can contain only expressions. Anything that is a statement needs a loop:

You need to…Why a comprehension cannot
break out earlybreak is a statement. Use a loop, or next() with a generator for the "find the first" case.
Handle exceptions per itemtry/except is a statement. Use a loop, or move the risky part into a helper function.
Assign to a variable mid-wayAssignment is a statement. The walrus operator := is the only in-expression alternative, and it rarely helps readability.
Build more than one outputA comprehension produces one collection. Two loops, or one loop with two appends, is clearer.
Add a comment per stepThere is nowhere to put one. That is usually the sign the logic belongs in a loop.
# needs try/except per item — write the loop
parsed = []
for raw in lines:
    try:
        parsed.append(int(raw))
    except ValueError:
        log_skip(raw)

# "find the first match" — next() with a generator, no loop needed
first = next((u for u in users if u["id"] == target), None)

There is also a debugging argument. A comprehension that raises gives you one line in the traceback with no way to see the offending item; a loop lets you print as you go. While you are still working out the logic, the loop is the better tool — convert it afterwards if the result reads well.

Common mistakes

  1. Confusing filter with transform. [x for x in a if x > 0] shortens the list; [x if x > 0 else 0 for x in a] keeps its length. Putting the if in the wrong position is the most frequent error here.
  2. Reusing a comprehension variable afterwards. It does not exist — comprehensions have their own scope. If you need the last value, keep the loop.
  3. Building a grid with multiplication. [[0] * 3] * 2 creates two references to the same inner list, so writing to one row changes both. The comprehension form [[0] * 3 for _ in range(2)] builds independent rows.
  4. Calling a comprehension a generator. Square brackets build the whole list in memory right now; only parentheses give you the lazy behaviour.
  5. Nesting past two levels. It parses, but nobody can read it. Split into two comprehensions or write the loop.
  6. Shadowing a name you still need. [len for len in words] compiles and then leaves len as a number for the rest of the scope.

Comprehension or loop — quick test

Ask three questions. Is it a single expression per item? Is there at most one filter and at most two for clauses? Would you be able to read it aloud in one breath? Three yeses and the comprehension wins. Any no, and the for loop is the better code — not a failure of style, just the right tool.

The habit worth building is the reverse of the usual advice: write the loop first while you are figuring the logic out, then collapse it into a comprehension once it is correct and genuinely reads better. That way you get the clarity of the comprehension without debugging inside one.

Frequently asked questions

What is a list comprehension?

A one-expression way to build a new list from an iterable: [expression for item in iterable if condition]. It replaces the empty-list-plus-loop-plus-append pattern, runs a little faster, and keeps the loop variable from leaking into the surrounding scope.

Are comprehensions faster than a loop with append?

Usually yes, modestly — often 10 to 30 percent, because the repeated list.append lookup and call per iteration disappear. Readability is the stronger argument, though.

When should I not use one?

When you need break, try/except, an assignment, or several filters with comments. Those are statements, and a comprehension only allows expressions. If it needs explaining, write the loop.

List comprehension or generator expression?

Brackets build the entire list in memory immediately; parentheses return a lazy generator that produces one item at a time. Use the generator for large or infinite data, or when passing straight into sum(), max(), any() or join().

How do I write a nested one?

Add the for clauses left to right, outer loop first: [x for row in grid for x in row] flattens a grid. Beyond two for clauses, the nested loop is easier to read.

Practise it

Reading about comprehensions is the easy half. These free exercises put the same rules in front of you as bugs to fix, in the browser, with no setup: dictionary comprehension with a condition, division by zero inside a comprehension, average of an empty filtered list and sorted() with a key function. The lists and dictionaries path sequences them properly, and the output-prediction trainer covers the comprehension snippets interviewers like to set.