Command Palette

Search for a command to run...

Hectal
PHASE 4Beginner ~29 min· topic 7 of 11

Topic 4.7

List, Dict and Set Comprehensions

In one line

A comprehension builds a new list, dictionary or set from another collection in one readable line: what to produce, where to take items from, and an optional filter. It replaces the common loop-and-append pattern, as long as it stays short enough to read at a glance.

Think of it like this

A teacher says "write down the square of each number on the board, but only the even ones". She does not describe a loop with an empty page and a pen; she states the result she wants in one sentence. A comprehension is that sentence in Python: [n * n for n in numbers if n % 2 == 0].

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Comprehension
A short expression that builds a list, dictionary or set from another collection.
Expression
A piece of code that produces a value, such as n * n or len(w).
Filter
The optional if at the end of a comprehension, which keeps only some items.
Conditional expression
a if condition else b: gives a when the condition is true, otherwise b.
Nested comprehension
A comprehension with more than one for, or one inside another.
Flatten
Turn a list of lists into one list with all the items.
Scope
The part of a program where a name can be used. A comprehension's loop variable lives only inside it.
Generator expression
A comprehension in round brackets that produces items one at a time instead of a full list.
Side effect
Anything a piece of code does besides giving a value, such as printing or changing a list.

Step by step

01From a loop to a comprehension

The four-line loop and the one-line comprehension below it do the same job. The comprehension puts the result first (n * n), then says where the items come from (for n in numbers).

main.pywhole filepython
numbers = [1, 2, 3, 4, 5, 6]

# the loop way
squares = []
for n in numbers:
    squares.append(n * n)

# the comprehension way
squares = [n * n for n in numbers]
From a loop to a comprehensiondiagram
Rendering diagram…

02Filter with if

The if at the end decides which items get through. Only items where the condition is true reach the expression.

main.pywhole filepython
numbers = [1, 2, 3, 4, 5, 6]
evens = [n for n in numbers if n % 2 == 0]
big_squares = [n * n for n in numbers if n > 3]
print(evens)         # [2, 4, 6]
print(big_squares)   # [16, 25, 36]

03if-else goes in front

When you want to change an item instead of dropping it, the choice goes in the expression part. Compare the two positions: if at the end filters, if ... else at the front chooses a value.

main.pywhole filepython
numbers = [1, 2, 3, 4]
labels = ["even" if n % 2 == 0 else "odd" for n in numbers]
print(labels)        # ['odd', 'even', 'odd', 'even']

04Dictionary and set comprehensions

Curly brackets with key: value build a dictionary. Curly brackets with a single expression build a set (duplicates vanish). Remember to print a set with sorted(...).

main.pywhole filepython
words = ["apple", "Banana", "cherry", "avocado"]
lengths = {w: len(w) for w in words}
initials = {w[0].lower() for w in words}
print(lengths)
print(sorted(initials))   # ['a', 'b', 'c']

05Two for clauses

Multiple for clauses act like nested loops, outer first. This makes every pair, and also flattens a grid.

main.pywhole filepython
pairs = [(x, y) for x in [1, 2, 3] for y in "ab"]
matrix = [[1, 2, 3], [4, 5, 6]]
flat = [v for row in matrix for v in row]
print(len(pairs), flat)    # 6 [1, 2, 3, 4, 5, 6]

06The loop variable stays inside

The x inside the comprehension is a different x from the one outside. This protects your variables from being overwritten by accident. A normal for loop does not give this protection.

main.pywhole filepython
x = "outer"
squares = [x * x for x in range(3)]
print(x)             # still outer

07When not to use one

Three filters and two loops in one line is a puzzle, not code. If a teammate needs more than a few seconds to say what a comprehension produces, split it into a loop with clearly named steps. Clarity beats cleverness.

Try it yourself

  1. 1

    Filter versus choose

    In the first example, change labels so it keeps only the odd numbers (using if at the end), then change it back to labelling all of them (using if ... else at the front). Say out loud what is different about the length of the result.

  2. 2

    Write one yourself

    Make a list of the first 5 square numbers with range. Then make a dictionary that maps each number from 1 to 5 to its cube. Check against [1, 4, 9, 16, 25] and {1: 1, 2: 8, 3: 27, 4: 64, 5: 125}.

Code & diagrams

List, filter and conditional comprehensions New tab

Change the filter to n > 2 or the labels to your own words, and predict the output before running.

Sign in to run this example in your browser.

Expected output

squares: [1, 4, 9, 16, 25, 36]
evens: [2, 4, 6]
labels: ['odd', 'even', 'odd', 'even', 'odd', 'even']
upper: ['APPLE', 'BANANA', 'CHERRY', 'AVOCADO']
long words: ['Banana', 'cherry', 'avocado']
pairs: [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b'), (3, 'a'), (3, 'b')]
flat: [1, 2, 3, 4, 5, 6]
sum of squares: 91
Dict and set comprehensions, and scope New tab

In the last lines, `[0] * 3` makes a list of three zeros, and the comprehension builds a fresh row each time. Topic 4.9 explains why that matters.

Sign in to run this example in your browser.

Expected output

lengths: {'apple': 5, 'Banana': 6, 'cherry': 6, 'avocado': 7}
initials: ['a', 'b', 'c']
cheap: {'apple': 30, 'banana': 12}
flipped: {30: 'apple', 12: 'banana', 50: 'mango'}
squares: [0, 1, 4, 9] | x is still outer
independent rows: [[1, 0, 0], [0, 0, 0]]
Turn raw log lines into data New tab

Real logs arrive as text lines. Three comprehensions pull out the levels, the users who had errors, and a count per day. `sum(1 for ...)` counts how many items pass a test.

Sign in to run this example in your browser.

Expected output

levels: ['INFO', 'ERROR', 'INFO', 'ERROR', 'WARN']
users with errors: ['asha', 'ravi']
lines per day: {'2026-03-01': 2, '2026-03-02': 3}

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Use the loop variable after the comprehension

Print the loop variable of a comprehension afterwards.

main.pywhole filepython
squares = [n * n for n in range(3)]
print(n)
terminal
$ python main.py
── what you'll see ──
Traceback (most recent call last):
File "main.py", line 2, in <module>
print(n)
^
NameError: name 'n' is not defined

Break #2

Put else at the end of a filter

Try to write a filter with an else branch.

main.pywhole filepython
numbers = [1, 2, 3, 4]
result = [n for n in numbers if n > 2 else 0]
terminal
$ python main.py
── what you'll see ──
File "main.py", line 2
result = [n for n in numbers if n > 2 else 0]
^^^^
SyntaxError: invalid syntax

Myth vs fact

Myth

A comprehension is always better than a loop.

Fact

It is better when it is short and builds one collection. Long or multi-step logic is clearer as a loop.

Myth

[print(n) for n in numbers] is a neat way to print.

Fact

It builds a list full of None just to throw it away. Use a normal for loop for side effects.

Myth

The loop variable leaks out of a comprehension like in a for loop.

Fact

In Python 3 it does not: a comprehension has its own scope. (In Python 2 list comprehensions leaked.)

Myth

(n * n for n in numbers) is a tuple comprehension.

Fact

There is no tuple comprehension. Round brackets make a generator expression; use tuple(...) if you want a tuple.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    A list comprehension is compiled to tighter bytecode than the equivalent loop with append (no repeated method lookup), so it is usually somewhat faster. Since Python 3.12 (PEP 709) comprehensions are inlined into the surrounding function instead of creating a hidden function, which makes them cheaper still (the PEP reports up to about twice as fast for tiny comprehensions); the scoping rules stay the same, but a traceback no longer shows a separate <listcomp> frame. Two scope facts still matter: the walrus operator := inside a comprehension binds its name in the enclosing function ([(total := total + n) for n in nums] updates total), and inside a class body a comprehension cannot see the class's own variables except in its first for ... in part.

  • ▸

    A dict comprehension silently drops duplicate keys, the later value winning: {p: n for n, p in prices.items()} loses entries when two names share a price (the "flipped" example above only works because the prices differ). A comprehension also evaluates its first iterable once, up front, and then walks it; changing the source list from inside the comprehension is as dangerous as changing a list in a loop (Topic 4.9).

  • ▸

    A comprehension builds the entire list in memory. For large or unending data, use a generator expression with sum, any, all, max or join, which uses constant memory (Phase 9).

  • ▸

    any(x > 3 for x in numbers) stops at the first True, while any([x > 3 for x in numbers]) builds the whole list first. Prefer the generator form inside those functions.

  • ▸

    In data and AI code, comprehensions turn API responses into the shape you need: [m["content"] for m in messages if m["role"] == "user"]. With NumPy and pandas you will usually replace such loops with whole-array operations (Phase 14), which are much faster.

Remember this

  1. 1

    A list comprehension has the shape [expression for item in collection]. An expression is any piece of code that produces a value, such as n * n. Read the comprehension like English: "give me expression, for each item in collection". [n * n for n in [1, 2, 3]] is [1, 4, 9]. It is exactly equivalent to making an empty list, looping, and appending, but shorter and usually a little faster.

  2. 2

    Add if at the end to filter: [n for n in numbers if n % 2 == 0] keeps only the items for which the condition is true. Items that fail the filter are skipped, not replaced. Do not confuse this with the conditional expression a if condition else b (Topic 2.2), which picks one of two values and goes at the front (in the expression part): ["even" if n % 2 == 0 else "odd" for n in numbers] gives one result for every item.

  3. 3

    You can use more than one for, which behaves like nested loops, and the first for is the outer loop: [(x, y) for x in [1, 2, 3] for y in "ab"] makes six pairs. A common use is flattening a list of lists: [v for row in matrix for v in row]. The rule of thumb: read the for clauses left to right as if they were written as nested loops, top to bottom.

  4. 4

    The same idea builds other containers. A dict comprehension uses curly brackets and key: value: {w: len(w) for w in words}. A set comprehension uses curly brackets with one expression: {w[0] for w in words}. Round brackets do something different: (n * n for n in numbers) is a generator expression, which makes items one at a time instead of building the whole list. You can pass one straight to sum(...); Phase 9 explains generators.

  5. 5

    A comprehension has its own scope: the loop variable exists only inside it. After squares = [n * n for n in range(3)], the name n does not exist (or, if you had an n before, it keeps its old value). This is different from a normal for loop, where the loop variable stays alive after the loop ends.

  6. 6

    Comprehensions should make code easier to read. If one needs more than one filter, more than two for clauses, or a long expression, write a normal loop instead. Never use a comprehension just for its side effects, such as printing inside it; that builds a list of None you throw away.

Explain it without notes

01

Rewrite a loop that appends squares of even numbers as a comprehension, and explain how to read it.

02

What is the difference between [n for n in nums if n > 2] and [n if n > 2 else 0 for n in nums]?

03

How does the order of two for clauses in a comprehension work?

04

Is the loop variable of a comprehension visible afterwards?

05

What does funcs = [lambda: i for i in range(3)] give when you call each function, and why?

Practice

01

Make a list of the cubes of 1 to 5 using a comprehension and range.

02

Given words = ["sun", "moon", "star", "sky"], build a list of the words that start with "s", in upper case.

03

Given prices = {"pen": 10, "book": 80, "bag": 500}, build a new dictionary with every price increased by 10 percent (use price * 1.1 and round(..., 2)).

Trade-offs

  • ↔

    Short and fast, but only while short. A one-line comprehension is easier to read than four lines of loop. Past two clauses it becomes harder than the loop.

  • ↔

    Builds everything at once. A list comprehension holds the whole result in memory. For huge data, a generator expression trades the ability to index and reuse the result for constant memory.

Done when you can

  • Done when you can turn a loop that appends into a list comprehension and back.

  • Done when you can tell a filter if from an if ... else in the expression.

  • Done when you can write a dict and a set comprehension.

  • Done when you can read a comprehension with two for clauses.

  • Done when you can say that the loop variable does not leak and when you would use a loop instead.