Building a Collection in One Line
Building a new list from an old one usually starts as three lines: an empty list, a loop, and an append. Write the same transformation as one line, and read it back exactly as fast as you wrote it.
Read first: Doing Something More Than Once
Building a new list from an old one usually starts as three lines: an empty list, a loop, and an append. Write the same transformation as one line, and read it back exactly as fast as you wrote it.
The three-line version, first
squares = [] starts empty. The loop runs once per number, and squares.append(n * n) grows the list by one each time. Nothing here is wrong — it is just three lines to say one idea.
squares = []for n in numbers: squares.append(n * n)The loop version
squares = []
for n in numbers:
squares.append(n * n)The comprehension
squares = [n * n for n in numbers]squares
Both versions produce the exact same list. The comprehension just says it in the order you would say it out loud: what to compute, then what to loop over, then which ones to keep.
The same idea, written as one expression
[n * n for n in numbers] is a list comprehension: the same loop and the same append, written in the order you would say it aloud — “n squared, for every n in numbers”. Adding if n % 2 == 0 at the end filters which values make it in, doing the work of the loop's if check without a separate line.
$ >>> numbers = [1, 2, 3, 4, 5, 6]$ >>> squares = [n * n for n in numbers]$ >>> squares$ [1, 4, 9, 16, 25, 36]$ >>> evens_squared = [n * n for n in numbers if n % 2 == 0]$ >>> evens_squared$ [4, 16, 36]n * n — what to compute and put in the new list, evaluated once per item kept.
for n in numbers — the loop, unchanged from the three-line version.
if n % 2 == 0 — optional. Only items that pass are computed and kept; the rest are skipped entirely.
Dict and set comprehensions follow the same shape
The square-bracket version builds a list, but the same idea works with curly braces too. Add a colon between two expressions and you get a dict comprehension; leave the colon out and you get a set comprehension instead.
$ >>> names = ["Ada", "Grace", "Alan"]$ >>> lengths = {name: len(name) for name in names}$ >>> lengths$ {'Ada': 3, 'Grace': 5, 'Alan': 4}$ >>> unique_lengths = {len(name) for name in names}$ >>> unique_lengths$ {3, 4, 5}{name: len(name) for name in names} builds a dictionary the same way the loop version would — one key, one value, per iteration. Drop the key entirely and keep only a value, and a set comprehension collapses duplicates the same way set() always does: three names with two distinct lengths in unique_lengths produce two entries, not three.
The nested case: two loops in one expression
A comprehension can hold more than one for clause, in the same order you would nest the loops. The most common reason is flattening — turning a list of lists into a single list.
rows = [[1, 2, 3], [4, 5], [6]] flat = []for row in rows: for value in row: flat.append(value)$ >>> flat = [value for row in rows for value in row]$ >>> flat$ [1, 2, 3, 4, 5, 6]Read the two for clauses left to right, in the same order the nested loops ran: the outer loop — for row in rows — comes first, then the inner one — for value in row. Reverse them and Python raises a NameError, because row has to exist before for value in row makes sense.
When a comprehension makes code harder to read, not easier
A comprehension earns its place when the body is one short expression. Nest two loops inside it, or stack three conditions, and it stops being readable in one glance — at that point the three-line loop is the clearer choice, not a worse one.
# technically one line — not obviously one ideareport = [f"{n}: {'even' if n % 2 == 0 else 'odd'}" for n in numbers if n > 2]Nothing about that line is illegal, and nothing about it is fast to read either — a condition inside the expression, a filter at the end, and a format string all competing for attention in one clause. The three-line loop version of the same logic reads in the order your eye actually moves.
Key takeaways
- A list comprehension is a loop and an append, written as one expression in the order you'd say it aloud: what to compute, what to loop over, what to keep.
- [expression for item in iterable if condition] — the if is optional, and only items that pass it are computed and kept.
- Curly braces build a dict comprehension with a colon ({k: v for ...}) or a set comprehension without one ({v for ...}) — the same shape, a different bracket.
- A comprehension can nest more than one for clause, read left to right in the same order the equivalent nested loops would run — useful for flattening, unreadable past two levels.
- Once the body needs nested loops or several conditions, a plain loop reads more clearly than a comprehension does — that's not a failure of comprehensions, it's the line where they stop being the right tool.
Quick check
Answer these to unlock the next chapter — 3 of 4 to pass. You can retake it anytime.
Answer every question to check.
Make a free account to read on
Every chapter is free — an account is how your progress, XP, and streak follow you from your laptop to your phone, and how you show up on the leaderboard. No payment, no trial.