Intuition The ONE core idea
A list comprehension is a factory on a conveyor belt : items flow in from a source, an optional guard throws some away, and a machine reshapes each survivor — and at the end you hold a brand-new list. By the end of this page you will be able to read the one-line recipe [expr for x in iterable if condition] as plain English — but every one of those words (expr, iterable, condition) is defined below before we ever lean on it.
Before you can read that one-liner, you must be fluent in the small pieces it is built from. This page assumes you know nothing and earns every symbol, one at a time, in the order they depend on each other. We will only assemble the full recipe in the final section, once every part has a meaning.
Definition Value and literal
A value is a single piece of data your program can work with — a number like 5, or a string (text) written in quotes like "apple". When you write the data out directly in your code (5, "apple", True), that written-out form is called a literal — it literally is the value, no computation needed.
The picture: a value is one physical object — a coin (5), a written word ("apple") — that you can hold, pass around, or drop into a box.
Definition The assignment operator
=
A single = sticks a name onto a value . x = 5 is read "let the name x refer to the value 5". It is a command ("make it so"), not a question. This is different from == (two equals), which you'll meet in §5 and which asks a question.
Intuition Why the topic needs this
Every item that flows through a comprehension is a value , and the built list is a box of values. We also use = throughout this page (e.g. r = [] to make an empty box) — so both must be defined before we lean on them. Every code line below rests on knowing what a value is and what = does.
A list is an ordered box of values , written by putting values inside square brackets: [10, 20, 30]. "Ordered" means each value has a fixed position (a first, a second, a third), so [1, 2] is a different list from [2, 1].
,
The comma is pure syntax that separates one item from the next — it is not part of any item's value. In [10, 20, 30] there are three items (10, 20, 30); the commas are just fences between them. Without commas Python could not tell where one item ends and the next begins.
The picture: think of a row of numbered lockers, with a thin divider (the comma) between each pair. Locker 0 holds the first item, locker 1 the second, and so on (Python starts counting at zero , not one — remember that).
Figure s01 — a list drawn as a row of numbered lockers. Each locker holds one item; the thin dividers are the commas; the index label under each locker counts from 0.
Intuition Why the topic needs this
A list comprehension builds a new list . The outer [ ] in the recipe are the same square brackets — they are the walls of the box being filled — and the built list's items are separated by the same commas. If you didn't know what [ ] and , meant, the whole notation would be a mystery from character one.
A variable is a name that points at a value . Writing x = 5 (using the = from §0a) means "stick the label x onto the value 5". Later, whenever you write x, Python looks at what the label is stuck to and uses that.
The picture: a luggage tag. The tag says x; it can be moved from one suitcase (value) to another. During a loop, the tag x gets re-stuck to each item in turn.
Intuition Why the topic needs this
In the recipe, the letter x is exactly this luggage tag. On each pass it re-attaches to the next item, so the output part can talk about "the current item" by just saying x. Without the idea of a re-stickable name, the for x part is meaningless.
Common mistake Steel-man: "
x is a fixed thing, like a constant."
It feels fixed because it's one letter. Reality: inside a loop x changes on every pass — it is the current item, not one frozen value. Fix: read for x in ... as "let x become each item, one at a time".
Common mistake Steel-man: "After a comprehension,
x still holds the last item, like it does after a plain for loop."
After a plain for x in ...: loop, x does leak out and keeps its last value. But in a list comprehension (Python 3), the loop variable x is isolated to the comprehension's own private scope — it does not leak into the surrounding code. Reality:
x = 99
squares = [x ** 2 for x in range ( 3 )] # x here is private
print (x) # still 99, NOT 2
Fix: treat the comprehension's x as a name that lives and dies inside the brackets; it never overwrites an outer x of the same name in Python 3.
An iterable is anything you can pull items out of, one by one, in order . A list [1,2,3] is iterable (pull 1, then 2, then 3). A string "abc" is iterable (pull 'a', 'b', 'c').
The picture: a Pez dispenser or a conveyor belt — you don't grab everything at once, you take the next item, then the next.
range(n)
range(n) is a special iterable that hands out the whole numbers ==starting at 0 and stopping before n==. So range(4) gives 0, 1, 2, 3 (four numbers, but not 4).
Figure s02 — range(4) as a dispenser handing out 0, 1, 2, 3 in order; the greyed-out 4 shows the stop value is never produced.
Common mistake Steel-man: "
range(4) includes 4."
It feels natural to count to 4. Reality: range(n) stops one short — it gives 0,1,2,3. The count of items equals n, but the last value is n-1. Fix: say it aloud as "up to but not including n".
Definition Generator (plain-words)
A generator is a one-shot iterable that produces its items lazily — it makes each item only at the moment you ask for it, and once you've walked through it, it's used up (you cannot loop it a second time or ask its length). Contrast a list, which stores every item up front and can be reused.
Intuition Why the topic needs this
The for x in iterable slot needs a source of items. In the parent's example range(10) is that source. Note that range is not a generator — it returns a reusable sequence object (you can loop over the same range twice, ask its length, or index it), it just doesn't store all the numbers at once. That distinction is exactly the bridge to Generator expressions later, which really are one-shot and lazy in the sense just defined.
See range() and iterables for the full story of what can feed the for slot.
for loop
A for loop repeats a block of code once for each item of an iterable. The syntax is:
for x in iterable:
body # runs once per item, with x set to that item
: and indentation
The colon : at the end of the for line announces "a block of code follows" — it is required punctuation that opens the loop's body. The lines that belong to that body are then marked by indentation (a consistent number of leading spaces). Python has no { } braces like some languages; instead, the colon says "here comes a block" and the indentation says "these indented lines are that block" . Un-indent, and you've stepped back out of the loop.
The picture: the colon is a doorway sign reading "loop body this way ↓", and the indentation is the walled-off room the sign points into. A factory worker takes each item off the belt in turn, does the indented job on it, and reaches for the next.
Figure s03 — the for loop as a conveyor belt: items 0, 1, 2 arrive one at a time, the loop variable x becomes each in turn, and the body runs once per item.
Intuition Why the topic needs this
A list comprehension is a for loop wearing a disguise. The parent's whole "derivation from the loop" only makes sense once you can trace a for loop by hand: pick item, run body, repeat. Everything else is sugar on top. (The comprehension drops the colon and the indentation, because it squeezes the whole thing onto one line.) See For loops .
Worked example Trace it once by hand
for x in range ( 3 ):
print (x)
Pass 1: x becomes 0, print 0. Pass 2: x becomes 1, print 1. Pass 3: x becomes 2, print 2. Then range(3) is empty, so the loop stops. Output: 0 1 2.
.append(item)
.append adds one item to the end of an existing list . Starting from an empty list r = [] (using = from §0a), the call r.append(7) makes r become [7]; a second r.append(9) makes it [7, 9].
The picture: dropping one more apple into the back of the box each time.
Intuition Why the topic needs this
The parent's "honest loop" starts with r = [] (empty box) and calls r.append(expr) on each pass. The list comprehension hides both the empty box and the append — but they are still happening. Knowing they exist lets you rewrite any comprehension back into a loop when it gets confusing (the parent's recommended debugging trick).
A Boolean is a value that is either ==True or False== — nothing else. A condition is any expression that evaluates to a Boolean, like x > 0 ("is x greater than 0?") or x % 2 == 0 ("is x even?").
The picture: a light switch or a yes/no gate. True = gate open (let it through), False = gate shut (throw it away).
Definition The operators inside conditions
> greater than · < less than · >= at least · <= at most
== equal to (two equals signs — this asks a question , unlike the single = from §0a)
!= not equal to
% the remainder after division: 7 % 2 is 1, 8 % 2 is 0
Common mistake Steel-man: "
= and == are the same."
They look alike, so it's a classic trap. Reality: = assigns (x = 5 sticks the label), while == asks ("are these equal?") and returns True or False. Fix: one equals = "make it so"; two equals = "is it so?".
Common mistake Steel-man: "
% always matches the maths modulus, even for negatives."
For non-negative numbers it does. But Python's % follows the sign of the divisor , so -7 % 3 is 2 (not -1), while some other languages give -1. Fix: when negatives can appear, don't assume; test it. For the even-check x % 2 == 0 this is harmless (an even number's remainder is 0 either way), but keep it in mind for filters on possibly-negative data.
Intuition Why the topic needs this
The if condition slot is the guard at the conveyor belt. It needs something that answers yes/no, and that is exactly a Boolean. x % 2 == 0 is the parent's "is it even?" gate. Without Booleans, filtering has no meaning.
Definition Expression vs statement
An expression is a piece of code that ==computes and produces a value ==: x**2, len(w), n if n>0 else 0 all become some value . A statement does something but produces no value to hand back: x = 5 or for ...: are statements.
The picture: an expression is a vending machine — put stuff in, a value comes out. A statement is a light switch — it acts, but you can't "hold" its result.
Definition The conditional (ternary) expression
a if cond else b
A ternary conditional expression picks between two values based on a condition. Its exact syntax is:
a if cond else b
Evaluation order: Python first evaluates cond (a Boolean from §5). If cond is True, the whole thing produces the value a; if cond is False, it produces the value b. Crucially it ==always produces one value == — that is why it is an expression , not a statement. Example: n if n > 0 else 0 gives n when n is positive, otherwise 0.
Intuition Why this is the KEY to the whole topic
The output slot must be an expression , because its value is what gets appended into the new list. This is why the parent insists that if/else inside the output must be the ternary a if cond else b (an expression that yields a value) and not a bare if: statement (which yields nothing). The whole "Choose-before, Cut-after" rule is really "expressions go in the value slot, filters go in the guard slot". See Conditional expressions (ternary) .
Figure s04 — left: an expression (vending machine) always hands back a value, so it can fill the output slot; right: a statement (switch) acts but returns nothing, so it cannot.
Definition Common expression pieces
x**2 means ==x raised to the power 2== (i.e. x times x). So 3**2 is 9.
len(w) gives the number of items in something: len("kiwi") is 4, len([5,5,5]) is 3.
Intuition Why the topic needs this
These are just sample output expressions the parent uses to show transformation . Any expression can sit in the output slot; x**2 and len(w) are simply the two the examples happen to reach for. The template doesn't care which — it only cares that the output part produces a value.
Now every piece is earned, so we can finally assemble the recipe [expr for x in iterable if condition] and read the parent's flagship example left to right, naming each part:
Fragment
What you now know it means
Built in
[ ... ] and its ,
building a new list of values , comma-separated
§0a, §0b
x**2
the expr: an expression producing a value
§6, §7
for x in range(10)
the loop over an iterable, x re-labels each item
§1, §2, §3
if x % 2 == 0
the Boolean guard keeping only survivors
§5
You can read it as one English sentence: "the value x**2, for each x from 0 up to 9, but only if x is even."
if guard is OPTIONAL
You may ==drop the if condition entirely==. Written as [expr for x in iterable] (no guard), every item is kept — the conveyor belt has no guard, so nothing is thrown away. Example: [x**2 for x in range(4)] gives [0, 1, 4, 9] (all four items transformed, none filtered).
Intuition Where this generalizes next (multiple
for, multiple if)
The conveyor-belt picture stretches naturally: you can chain more than one for (belts feeding belts — nested loops, e.g. [cell for row in grid for cell in row]) and more than one if (several guards in a row, all must pass). The leftmost for is the outermost loop. You don't need these yet — the parent note covers them — but know the recipe scales beyond the single-for, single-if shape shown here.
value literal and = assignment
Square brackets and comma = a list
Variables a re-stickable name x
Iterables range and generator
colon and indentation block
Expression statement and ternary
Test yourself — cover the right side and answer each aloud before revealing.
What is a value , and what is a literal ? A value is one piece of data (a number, a string); a literal is that value written out directly in code, like 5 or "apple".
What does a single = do? It assigns — sticks a name onto a value, e.g. x = 5. It is a command, not a question.
What does [ ] around comma-separated values build? An ordered list — a box of values, each with a fixed position, counting from 0.
Is the comma part of an item's value? No — it is pure syntax that separates one item from the next.
What is x in for x in ..., fixed or changing? Changing — it re-attaches to each item in turn, one per pass.
Does a list comprehension's loop variable x leak into the outer scope? No — in Python 3 it lives in the comprehension's private scope and does not overwrite an outer x of the same name.
What does range(4) produce? 0, 1, 2, 3 — starts at 0, stops before 4.
Is range a generator? No — it is a reusable sequence object; you can loop it twice, index it, or take its length.
What is a generator in one phrase? A one-shot, lazy iterable — it makes each item only when asked and is used up after one pass.
What is an iterable in one phrase? Anything you can pull items out of one at a time, in order.
What does the colon : at the end of a for line do? It announces that a block of code (the loop body) follows; the indented lines beneath it are that block.
What does a for loop do? Runs its indented body once for each item of an iterable, with the loop variable set to that item.
Starting from r=[], what does r.append(7) do? Adds 7 to the end, giving [7].
What two values can a Boolean be? True or False only.
Difference between = and ==? = assigns a value to a name; == asks "are these equal?" and returns a Boolean.
What does x % 2 == 0 test, and what caveat has % with negatives? Whether x is even; % takes the sign of the divisor, so e.g. -7 % 3 is 2, not -1.
What is the syntax and evaluation order of a ternary a if cond else b? Python evaluates cond first; if True the whole expression yields a, else it yields b — it always produces one value.
Why must the output expr be an expression, not a statement? Because its produced value is what gets appended into the new list; a statement produces no value to store.
Can you omit the if guard? Yes — [expr for x in iterable] keeps every item.
Where does a ternary a if cond else b fit, and why? In the expr slot (before for), because it is an expression that yields a value.
What is 3**2? 9 — 3 raised to the power 2.
For loops — the engine this whole topic is sugar over.
range() and iterables — what feeds the for x in iterable slot.
Conditional expressions (ternary) — the expression-form if/else used inside expr.
map() and filter() — functional equivalents of transform (§7) and filter (§5).
Dictionary and set comprehensions — same foundations, different brackets.
Generator expressions — the lazy cousin; unlike range, truly one-shot (§2).
[[List comprehensions — `[expr for x in iterable if condition]` (index 1.2.34)|↑ Back to the parent topic]]