1.2.23 · D2Introduction to Programming (Python)

Visual walkthrough — Tuples — immutability, use cases

1,952 words9 min readBack to topic

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.

Figure — Tuples — immutability, use cases

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.

Figure — Tuples — immutability, use cases

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?

Figure — Tuples — immutability, use cases

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.

Figure — Tuples — immutability, use cases

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:

  1. Store [0, 0] → "origin". Fingerprint is computed, say . The pair goes in pigeonhole 5.
  2. Later you edit the key: key[0] = 9. Now the key is [9, 0].
  3. Next time you look it up, the dictionary re-fingerprints: hash([9, 0]) gives, say, .
  4. It looks in pigeonhole 8empty. The entry is lost, sitting forgotten in pigeonhole 5.

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.

Figure — Tuples — immutability, use cases

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 ❌.

Figure — Tuples — immutability, use cases

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.

Figure — Tuples — immutability, use cases

can it change

can it change

frozen fingerprint

drifting fingerprint

dict finds it

dict loses it

value

immutable

mutable

hashable

unhashable

valid key YES

invalid key NO

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"?
Slot-writes are refused — t[0] = x raises TypeError.
Why must a dictionary key have a fingerprint that never moves?
The dict jumps to a pigeonhole by the hash; if the hash drifts, the entry is stored in one hole but searched for in another, and is lost forever.
Why can (1, [2, 3]) NOT be a dict key even though it's a tuple?
It contains a mutable list, so its contents (and thus its hash) could still change — Python refuses to hash it.
State the iron law of hashing from Step 4.
Equal values must always produce equal hashes, for their entire lifetime.
Summarise the whole chain in three words.
Frozen ⇒ Fingerprint ⇒ Findable.