Visual walkthrough — Dictionary and set comprehensions
Before any code, three plain words we will keep pointing at:
Step 1 — The undeniable starting point: an empty box
WHAT. We build a dictionary of squares the slowest, most obvious way — make an empty container, then fill it.
squares = {} # an empty dict — the boxWHY. We must start from something no one can argue with. {} is an empty dict (remember the empty-braces trap: {} is a dict, set() is a set). Right now it holds nothing — zero key→value arrows.
PICTURE. The box on the left is empty. The conveyor belt on the right holds the numbers 0,1,2,3,4 that range(5) will feed us, one at a time. Nothing has moved yet.

Step 2 — One item, one arrow
WHAT. Take the first item off the belt, n = 0, and store one entry: key 0 points to value 0*0 = 0.
n = 0
squares[n] = n * n # squares[0] = 0Term by term, on squares[n] = n*n:
squares:::: the box we are filling.[n]:::: which slot — the key, here0.= n*n:::: what goes in that slot — the value, here0.
WHY. This is the atom of the whole idea: one item off the belt creates one key→value arrow. Everything else is just repeating this.
PICTURE. A yellow arrow now runs from the button labelled 0 (the key) to the snack labelled 0 (the value). The belt has advanced by one.

Step 3 — Let the loop do it five times
WHAT. Wrap Step 2 in a for loop so every item on the belt gets its arrow.
squares = {}
for n in range(5): # walk the belt: 0,1,2,3,4
squares[n] = n * n # one arrow per passWHY. The loop is just Step 2 repeated. Pass 1 makes 0→0, pass 2 makes 1→1, pass 3 makes 2→4, and so on. The loop drives the box; nothing new is invented — only repetition.
PICTURE. All five arrows are drawn now: 0→0, 1→1, 2→4, 3→9, 4→16. Notice the loop has three moving parts, colour-coded: the belt source range(5) (blue), the key n (yellow), the value n*n (green).

Step 4 — The fold: rearrange, delete nothing
WHAT. Take the three coloured pieces from Step 3 and slide them into one line, changing their order but not their meaning.
squares = {n: n*n for n in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}WHY. Read the comprehension left to right: it says the result-rule first ("n : n*n, i.e. this key paired with this value") and the source last ("for each n on the belt"). That reversed order is the only new thing to get used to — the machinery is identical to the loop.
PICTURE. The three coloured blocks from the loop physically migrate: key n and value n*n fly up to the front (with a colon between them), the belt for n in range(5) slides to the back. Same arrows, same result — just re-packed into one line.

Step 5 — Add a gate: the filtering if
WHAT. Only let even numbers onto the assembly line by attaching an if at the end.
{n: n**2 for n in range(1, 11) if n % 2 == 0}
# {2: 4, 4: 16, 6: 36, 8: 64, 10: 100}Term by term on the tail for n in range(1,11) if n % 2 == 0:
for n in range(1,11):::: the belt now carries1..10.if n % 2 == 0:::: a gate —n % 2is the remainder after dividing by 2;== 0means "no remainder", i.e. even.
WHY. The if runs before the pair is built. If n fails the gate, no arrow is ever drawn for it — the odd numbers fall off the belt and never reach the box.
PICTURE. A gate sits on the conveyor. Even numbers (green) pass through and get their arrow; odd numbers (red) are bounced off before they can pair. The gate is at the end of the sentence — you read the source, then the gate, then know what survived.

Step 6 — Drop the colon: same animation, now a set
WHAT. Delete the key and the colon, keep only the value. The factory now builds a set.
words = ["cat", "dog", "tiger", "ox"]
{len(w) for w in words}
# {2, 3, 5}WHY. With no colon, each pass produces one value (len(w)), not a pair. A set stores only distinct values, so when "cat" and "dog" both give length 3, the second one is silently ignored — the set already has a 3. (See Sets in Python for why sets deduplicate, and Hashing and hashable types for how they know a value is "already there".)
PICTURE. Two lengths of 3 head into the set; the first is placed, the second bounces off the wall of an already-occupied slot. Compare the top row (dict: pairs with colon) to the bottom row (set: single values, no colon) — one glyph is the whole difference.

Step 7 — The degenerate & edge cases (never let the reader get surprised)
WHAT. Three boundary situations the animations must cover.
(a) Empty belt. If the iterable is empty, zero passes happen, so you get an empty collection — an empty dict {} or an empty set().
{n: n*n for n in range(0)} # {} (belt had nothing)
{x for x in []} # set()(b) Colliding keys — later wins. Two items producing the same key do not coexist; the later pass overwrites the earlier.
{x % 3: x for x in range(6)}
# expected 6 entries, got 3: {0: 3, 1: 4, 2: 5}
# key 0 got x=0 then x=3 -> 3 wins; key 1 got 1 then 4 -> 4; key 2 got 2 then 5 -> 5(c) Unhashable key — a crash, not a silent bug. A key must be hashable; a list is not.
{[1, 2]: "a"} # TypeError: unhashable type: 'list'
# fix: use a tuple, which IS hashable
{(1, 2): "a"} # fineWHY. These are exactly the moments a beginner thinks "the comprehension is broken." It isn't — it is obeying dict rules: unique hashable keys, later-writer-wins. A set version of (b) simply keeps one copy of each value by the same logic. (Ternary value-choosers like ("even" if n%2==0 else "odd") live in the value slot — see Ternary conditional expression.)
PICTURE. Left panel: the empty belt → empty box. Middle panel: two arrows aimed at key 0, the second (green) overwriting the first (faded), leaving one entry. Right panel: a list-shaped key hitting a red barrier stamped "unhashable", with the tuple-shaped key passing.

The one-picture summary
Everything on this page compressed into a single frame: a belt feeds items through an optional gate; each survivor becomes either a key:value pair (colon present → dict) or a single value (no colon → set); dicts enforce unique hashable keys, sets enforce distinct values.

Recall Feynman retelling — say it in one breath
Picture a conveyor belt feeding numbers one at a time. You stand at the belt with a box.
For each number, you decide two things: what button it gets (the key) and what snack it
produces (the value) — that's the key : value at the front. The for ... part is just the belt
telling you which numbers arrive. If you add an if at the very end, that's a gate: numbers that
fail it fall off before you ever pair them. If you drop the colon, you stop making pairs and just
toss single values into a bag that refuses duplicates — that's a set. Two items wanting the same
button? The later one shoves the earlier one out. Try to use a list as a button? The machine
jams, because buttons must be hashable. That whole story is what one line {k: v for x in xs if c}
is doing at C speed instead of you doing it slowly in Python.
Connections
- 1.2.35 Dictionary and set comprehensions (Hinglish) — parent topic, the rule stated
- List comprehensions — same fold, produces a list with
[] - Dictionaries in Python — the structure built by the colon form
- Sets in Python — the structure built by the no-colon form
- Generator expressions — same head/tail shape but lazy, in
(...) - Hashing and hashable types — why keys/elements must be hashable
- Ternary conditional expression — the value-slot
if/else