Visual walkthrough — Tuples — immutability, use cases
We will meet exactly three new words along the way. We define each one with a picture before we use it:
- a slot (a numbered pigeonhole that holds a value),
- a hash (a fingerprint number computed from a value),
- immutable vs mutable (whether the value can be edited in place).
Everything else is just following the arrows.
Step 1 — What "a collection of slots" actually means
WHAT. Both a tuple (3, 4) and a list [3, 4] are, at first glance, the same thing: a row of slots. A slot is one numbered pigeonhole. Slot number 0 holds the first value, slot 1 the second, and so on. Counting from 0 (not 1) is just Python's convention.
WHY start here. Before we can say what tuples forbid, we must see what a slot is, because the whole difference between tuple and list is about whether you may write into a slot.
PICTURE. Below, the same two values 3 and 4 sit in two identical-looking rows. The row on the left is a tuple (drawn with a locked border); the row on the right is a list (drawn open). Same contents — the difference is the rule stamped on the border, which we uncover next.

Step 2 — The one rule that separates them: can you write into a slot?
WHAT. Try to overwrite slot 0:
- On a list this succeeds — the pigeonhole now holds
99. - On a tuple this raises
TypeError— the pigeonhole refuses to be rewritten.
WHY. This single behaviour is the whole meaning of the words below. There is nothing more mystical to "immutable" than: slot-writes are refused.
PICTURE. The amber arrow tries to shove 99 into slot 0. On the list it lands. On the tuple it bounces off the locked border and Python throws an error.

Step 3 — A dictionary is a wall of numbered pigeonholes
WHAT. A dictionary stores key → value pairs. But it does not search the keys one by one. Instead it turns each key into a number and uses that number to jump straight to a pigeonhole. See Dictionaries — keys and values.
WHY introduce this now. To understand why the key must be immutable, we first need to see how the dictionary uses the key — it uses it to compute an address. Hold that thought; the next step gives the address a name.
PICTURE. A dictionary drawn as a wall of pigeonholes numbered 0,1,2,…. A key floats in; an arrow points to one specific pigeonhole. The question of the whole page becomes: what decides which pigeonhole?

Step 4 — The hash: a fingerprint number squeezed from a value
WHAT. The pigeonhole is chosen by a hash: a function that takes any value and spits out an integer fingerprint.
That number decides the pigeonhole. Its one iron law:
The symbol means "forces / guarantees." Read it: equal things must always fingerprint the same, and must keep fingerprinting the same for their whole life. See Hashing in Python.
WHY a hash and not a plain search? Because searching a wall of a million keys one-by-one is slow. A fingerprint lets the dictionary jump to the right pigeonhole in one step. Speed is the entire reason hashing exists.
PICTURE. The value (0, 0) enters a "hash machine"; out comes a number; that number points at pigeonhole. Same value in ⇒ same number out, drawn as two identical passes giving the identical arrow.

Step 5 — The disaster: what happens if a key could change
WHAT. Now imagine a key whose contents you are allowed to edit — a list. Here is the exact failure, step by step:
- Store
[0, 0] → "origin". Fingerprint is computed, say . The pair goes in pigeonhole5. - Later you edit the key:
key[0] = 9. Now the key is[9, 0]. - Next time you look it up, the dictionary re-fingerprints:
hash([9, 0])gives, say, . - It looks in pigeonhole
8— empty. The entry is lost, sitting forgotten in pigeonhole5.
WHY this is fatal. The dictionary's promise ("find any key instantly") is built on the fingerprint never moving. A mutable key breaks the law from Step 4 — the fingerprint drifts — so the entry becomes unreachable.
PICTURE. Two panels. Before edit: key fingerprints to 5, entry lands there. After edit: same key now fingerprints to 8; the arrow points to an empty hole while the real data sits stranded in 5, greyed out and unreachable.

Step 6 — The verdict: tuple ✅, list ❌ (and the shallow exception)
WHAT. Apply Step 5 backwards.
- A tuple of immutable items can never have its slots rewritten (Step 2) → its fingerprint never moves → it is hashable → valid key. ✅
- A list can have its slots rewritten → its fingerprint could move → Python refuses to fingerprint it at all →
TypeError: unhashable type: 'list'. ❌
d = {(0, 0): "origin"} # ✅ tuple key — frozen fingerprint
d = {[0, 0]: "origin"} # ❌ TypeError: unhashable type: 'list'The edge case. What about (1, [2, 3]) — a tuple that contains a list? Its second slot points at a mutable list, so its contents could still change (Step 2's shallow note). Python is careful: it refuses to hash such a tuple too.
hash((1, 2, 3)) # ✅ a number
hash((1, [2, 3])) # ❌ TypeError: unhashable type: 'list'WHY. The rule is not "is it a tuple?" but "can any part of it change?" A tuple qualifies as a key only if everything inside it is also immutable — the frozen border must go all the way down for the key to be safe.
PICTURE. A decision fork. Left branch: pure tuple (0,0) → all-frozen → green ✅ key. Middle: list [0,0] → open → red ❌. Right: tuple-with-list (1,[2,3]) → frozen border but a live list poking out → red ❌.

The one-picture summary
Read this figure left to right: it is the entire chain of reasoning on one line. Immutable → fingerprint frozen → dictionary can always find it → valid key. Any break in that chain (a mutable list, or a mutable object hiding inside a tuple) snaps the arrow and the key is rejected.

Recall Feynman retelling — say it to a 12-year-old
A dictionary is a huge wall of numbered mailboxes. To store something, it doesn't wander the wall looking for a spot — it stamps a number onto your key and drops the item in that exact mailbox. Later, to fetch it, it stamps the key again and goes straight to that mailbox.
The whole trick works only if stamping the same key always gives the same number. Now: a tuple is a sealed envelope — you can never change what's inside, so its stamp is always the same number. Perfect key. A list is an open box — you can swap its contents, so re-stamping it later gives a different number, and the dictionary looks in the wrong mailbox and finds nothing. That's why Python simply refuses to stamp a list at all.
One catch: if you seal a list inside an envelope ((1, [2, 3])), the envelope is stuck but the box inside can still gain crayons — so the stamp could still drift. Python spots this and refuses that envelope too. A key is safe only when it's frozen all the way down.
Connections
- Yeh page Hinglish mein
- Mutable vs Immutable objects — Steps 1–2 rest on this distinction.
- Hashing in Python — Steps 4–5 are hashing, in pictures.
- Dictionaries — keys and values — the machine whose need forces the rule.
- Sets — hashing & uniqueness — same hashability requirement for membership.
- Lists — mutability, methods — the mutable counterpart that fails as a key.
- Functions — return values — another everyday tuple use (packing/unpacking).
In the derivation, what single behaviour defines "immutable"?
t[0] = x raises TypeError.