Exercises — List comprehensions — `[expr for x in iterable if condition]`
Below, one picture shows the "conveyor belt" mental model we reuse — items flow left, the filter drops some, the transform reshapes the survivors into a new list.
![Figure — List comprehensions — `[expr for x in iterable if condition]`](/notes-assets/img/42b383a8a0218fe8.webp)
Level 1 — Recognition
You only need to read the template and match the four slots:
expr, for x in iterable, and the optional if condition.
L1.1
Recall Solution L1.1
expr=w.upper()— what goes into the new list.iterable=["hi", "yo"]— where eachwcomes from.condition=len(w) == 2— keepwonly if this isTrue.
Both words have length 2, so both survive → ["HI", "YO"].
L1.2
Recall Solution L1.2
No if, so every item is kept. We add 1 to each:
Answer: [11, 21, 31].
L1.3
Recall Solution L1.3
Here expr is just n, so survivors pass through unchanged. The test n > 5 keeps 7 and
9, drops 4 and 2.
Answer: [7, 9].
Level 2 — Application
Now you produce a comprehension or its exact output, including sign/edge behaviour.
L2.1
Recall Solution L2.1
Odd means x % 2 == 1 (the remainder after dividing by 2 is 1).
[x**2 for x in range(6) if x % 2 == 1]range(6) is 0,1,2,3,4,5. Odds are 1, 3, 5:
Answer: [1, 9, 25].
L2.2
Recall Solution L2.2
We are choosing a value, not dropping items, so we use a conditional expression
(a if cond else b) which lives before the for:
[n if n >= 0 else 0 for n in [3, -1, -4, 5]]- → keep
3 - ? No →
0 - ? No →
0 - → keep
5
Answer: [3, 0, 0, 5]. (See Conditional expressions (ternary).)
L2.3
Recall Solution L2.3
(a) The iterable is empty, so the loop body never runs → [].
(b) Five items are considered, none passes x > 100, so all are filtered out → [].
(c) range(0) produces no numbers at all → [].
Lesson: an empty result happens in two distinct ways — an empty source, or a filter that rejects everything. Both are legal, neither is an error.
Level 3 — Analysis
You must trace nested logic and reason about ordering and quantity.
L3.1
Recall Solution L3.1
Two fors read outer-to-inner, top-to-bottom — identical to nested loops:
for row in grid: # outer (leftmost for)
for cell in row: # inner
result.append(cell)Row by row: [1,2,3] → 1,2,3; [4] → 4; [] → (nothing); [5,6] → 5,6.
Answer: [1, 2, 3, 4, 5, 6], which has 6 elements. The empty inner list contributes nothing —
just like an inner loop over an empty list runs zero times.
L3.2
Recall Solution L3.2
Outer i runs 0,1,2; for each, inner j runs 0,1,2; keep only pairs with i < j.
i=0:j=1→(0,1),j=2→(0,2) (j=0 fails 0<0)i=1:j=2→(1,2) (j=0,1 fail)i=2: nojsatisfies2 < j
Answer: [(0, 1), (0, 2), (1, 2)] — exactly the 3 "strictly increasing" pairs.
L3.3
Recall Solution L3.3
By execution order the inner for y in range(x) binds y before the filter if y > 0
runs, so y is always defined when tested. Trace it:
x=0:range(0)is empty → noy.x=1:range(1)=0→y=0,0 > 0False → drop.x=2:range(2)=0,1→y=0dropped,y=1kept.
Answer: [1]. The rule: a filter if can use any variable already bound by a for to its
left.
Level 4 — Synthesis
Combine transform + filter + nesting, and choose the right tool.
L4.1
Recall Solution L4.1
"Divisible by 3" means x % 3 == 0. "Cube" means x**3.
[x**3 for x in range(1, 11) if x % 3 == 0]range(1, 11) = 1..10; multiples of 3 are 3, 6, 9:
Answer: [27, 216, 729].
L4.2
Recall Solution L4.2
Two ifs working together — filter if (after for) drops odds; ternary (before for) picks the
label:
["big" if n > 4 else "small" for n in [-5, -2, 0, 3, 8] if n % 2 == 0]Evens are -2, 0, 8 (note: -5 % 2 == 1, 3 % 2 == 1, so both odd → dropped).
-2 > 4? No →"small"0 > 4? No →"small"8 > 4? Yes →"big"
Answer: ["small", "small", "big"].
L4.3
Recall Solution L4.3
Comprehension (one English sentence):
[len(w) for w in ["cat", "dog", "banana", "sky"] if "a" in w]Words with "a": "cat" (3), "banana" (6). → [3, 6].
Functional equivalent:
list(map(len, filter(lambda w: "a" in w, ["cat","dog","banana","sky"])))Same result [3, 6], but the comprehension reads left-to-right as "give me the length for each
word if it contains a" — clearer than nesting map(filter(...)) inside-out. Prefer the
comprehension here.
Level 5 — Mastery
Build something a real program would use; reason about equivalence and laziness.
L5.1
Recall Solution L5.1
Main diagonal is (0,0), (1,1), (2,2). We nest two fors and filter them out with r != c:
[(r, c) for r in range(3) for c in range(3) if r != c]All 9 pairs minus the 3 diagonal ones = 6 pairs:
[(0,1),(0,2),(1,0),(1,2),(2,0),(2,1)].
Sanity count: — matches the list length.
L5.2
Recall Solution L5.2
(a) A is a list — all 1000 squares are built and stored immediately.
(b) B is a generator (see Generator expressions) — it stores a recipe, producing
squares one at a time, on demand, using almost no memory.
(c) . So sum(B) = 332833500.
(d) A generator is exhausted after one full pass. The second sum(B) sees nothing left →
0. (A list A could be summed again and again; a generator cannot.)
L5.3
Recall Solution L5.3
Comprehension trace (for → if → expr):
x=0: even → append0x=1: odd → skipx=2: even → append20x=3: odd → skip
→ [0, 20].
Loop it desugars to:
r = []
for x in range(4):
if x % 2 == 0:
r.append(x * 10)
# r == [0, 20]Identical output [0, 20]. This is the defining law: the comprehension is pure syntactic
sugar for that loop — same picks, same filter, same appends, same list.
Recall One-line self-test recap
Filter if position ::: after the for (no else) — decides whether to include.
Ternary if/else position ::: before the for, inside expr — decides what value.
Nested for order ::: leftmost for is the outermost loop, read top-to-bottom.
Two ways to get an empty result ::: empty source iterable, or a filter that rejects everything.
Generator reused after full consumption ::: yields nothing — it is exhausted.
Connections
- For loops — every solution above shows the equivalent loop.
- Conditional expressions (ternary) — the before-
forvalue chooser (L2.2, L4.2). - Dictionary and set comprehensions — same slot logic with
{}. - Generator expressions — the lazy
(...)version (L5.2). - map() and filter() — functional equivalents (L4.3).
- range() and iterables — feeds the
for x in iterableslot throughout.