This page is the exhaustive drill for Tuples — immutability, use cases . We will map out every kind of situation a tuple can throw at you — packing, unpacking, the sealed-slot rule, hashability, degenerate cases like the empty and one-element tuple — and then work each one to the end.
Intuition What "every scenario" means here
A tuple problem is really a question about three axes : how many items are inside (zero, one, many), whether you are reading or trying to change it, and whether the items themselves are frozen or mutable . If we cover every combination of those, nothing on an exam can surprise you.
Below is the full list of case classes this topic can generate. Every worked example is tagged with the cell it fills.
Cell
Case class
What makes it tricky
A
Packing values into a tuple
return a, b — no parens, still a tuple
B
Unpacking (exact / starred / mismatch)
count must match exactly, or use *rest
C
Degenerate: empty () and single (x,)
the trailing comma; (3) is not a tuple
D
Read-only ops build new tuples
slice, +, * never mutate the original
E
Illegal write → TypeError
t[0] = ... on a frozen slot
F
Shallow immutability
a list inside a tuple can still change
G
Hashability: tuple as dict/set key
works only if every item is hashable
H
Word problem (real-world record)
picking tuple vs list from meaning
I
Exam twist / limiting behaviour
count/index edge cases, ValueError
We now fill A–I with 9 worked examples.
Worked example 1 — Cell A: Packing without parentheses
Statement: What is the type and value of p after p = 3, 4, 5?
Forecast: Is p a tuple, a list, or a syntax error? Guess before reading.
What we did: wrote three values separated by commas on the right of =.
Why this step? Python's rule is that the comma (the , symbol) is the tuple-builder, not the parentheses. Seeing commas at the top level, Python packs them into one tuple.
What we did: bound that packed group to the single name p.
Why this step? One name on the left, three values on the right → they get gathered into one object.
Result: p == (3, 4, 5) and type(p) is tuple, len(p) == 3.
Verify: count the commas: two commas → three slots → len should be 3. ✅
Worked example 2 — Cell B: Unpacking — exact, mismatch, and starred
Statement: With pair = (10, 20), do three things: (a) unpack it into exactly two names, (b) try to unpack it into three names, (c) with record = ("Asha", 21, 5.6, "Delhi") gather the middle with a star.
Forecast: How many names must appear on the left in case (a), and what happens in case (b)?
What we did: x, y = pair — exact unpacking.
Why this step? When there is no star, the number of names on the left must equal the number of items on the right. Two items, two names → x == 10, y == 20.
What we did: a, b, c = pair — a deliberate mismatch .
Why this step? Three names but only two values means Python cannot fill c. It refuses and raises ValueError: not enough values to unpack (expected 3, got 2). This is the counterpart error to Cell E's TypeError.
What we did: name, *middle, city = record — a star target .
Why this step? The *middle says "give me a list of whatever is left after the fixed ones are matched," so the counts need not match exactly. Python matches name and city from both ends, then dumps the remainder into middle.
Result: (a) x == 10, y == 20; (b) ValueError; (c) name == "Asha", city == "Delhi", middle == [21, 5.6] (a list , always).
Verify: case (a) length matches (2 = 2); case (b) fails because 3 ≠ 2; case (c) leftover count = 4 − 2 = 2 → middle holds 2 items. ✅
Worked example 3 — Cell C: The degenerate cases (empty & single)
Statement: Give len and type for each of e = (), one = (42,), notatuple = (42).
Forecast: Which of these three is not a tuple?
What we did: e = () — empty parentheses.
Why this step? The empty tuple is the one case where parentheses are mandatory, because there is no comma to signal "tuple." So () is a special literal.
What we did: one = (42,) — a trailing comma after a single value.
Why this step? With one item there is nothing after it, so the comma itself does the signalling. Remove it and Python reads (42) as just grouping around the int.
What we did: notatuple = (42) — no comma.
Why this step? Parentheses here only group; the value is the plain integer 42.
Result: len(e) == 0, type(e) is tuple; len(one) == 1, type(one) is tuple; type(notatuple) is int.
Verify: type(notatuple).__name__ == "int" and type(one).__name__ == "tuple". ✅
Worked example 4 — Cell D: Read-only ops build a NEW tuple (slice, +, *)
Statement: After t = (1, 2, 3), what are s = t[0:2], u = t + (4,), and v = t * 2? Does t change?
Forecast: Which of these three operations edits t in place?
What we did: t[0:2] — a slice .
Why this step? A slice reads a range of positions (here slots 0 and 1) and copies them into a brand-new tuple . It reads but never writes, so t is untouched. In the figure, the top row is t; the slice pulls out a fresh short envelope.
What we did: t + (4,) — concatenation .
Why this step? + on tuples joins two tuples into a new one; it cannot touch the originals because they are frozen — a fresh, longer envelope is made.
What we did: t * 2 — repetition .
Why this step? * repeats the contents into another new tuple. Still no mutation of t.
Figure: t (top, sealed) never changes. The slice, the +, and the * each produce a separate new envelope below it — trace how none of the arrows point back into the original.
Result: t == (1, 2, 3) (unchanged), s == (1, 2), u == (1, 2, 3, 4), v == (1, 2, 3, 1, 2, 3).
Verify: len(s) == 2, len(u) == 4, len(v) == 6, and t is still length 3. ✅
Worked example 5 — Cell E: The illegal write
Statement: What happens when you run t = (1, 2, 3); t[0] = 99?
Forecast: Runtime error, silent success, or a new tuple?
What we did: t[0] = 99 asks Python to overwrite slot 0 .
Why this step? Indexed assignment demands the ability to write into a slot . Tuples publish no such ability.
What we did: observed the failure.
Why this step? Because the operation is forbidden at the type level, Python raises TypeError: 'tuple' object does not support item assignment — it never even reaches slot 0.
Result: TypeError is raised; t is untouched.
Verify: we catch the error and confirm its type is TypeError. ✅
Worked example 6 — Cell F: Shallow immutability
Statement: With t = (1, [2, 3]), run t[1].append(4). Then try t[1] = [9]. Predict each.
Forecast: Which line works? Both? Neither?
What we did: t[1].append(4) — reach through the tuple to the list inside, then mutate the list .
Why this step? The tuple only freezes which object each slot points to . Slot 1 still points to the same list object; changing that object's contents is allowed. In the figure, the arrow from slot 1 never moves, but the box it points to grows from 2 to 3 items.
What we did: t[1] = [9] — try to make slot 1 point at a different list.
Why this step? That is re-pointing a slot — exactly what immutability bans → TypeError.
Figure: the tuple's two slots have frozen pointers (arrows can't move). append changes the list box the pointer already aims at (allowed); reassigning t[1] would swing the arrow to a new box (forbidden).
Result: after line 1, t == (1, [2, 3, 4]); line 2 raises TypeError.
Verify: t[1] == [2, 3, 4] and len(t[1]) == 3; the reassignment raises TypeError. ✅
Worked example 7 — Cell G: Hashability boundary
Statement: Which of these can be a dict key? (0, 0), (1, (2, 3)), (1, [2, 3]).
Forecast: Two work, one crashes — which one?
What we did: tested (0, 0).
Why this step? A tuple is hashable only if every item inside is hashable . Two ints → hashable → valid key.
What we did: tested (1, (2, 3)) — a tuple nested in a tuple.
Why this step? The inner tuple is itself immutable/hashable, so the whole thing is hashable. Nesting frozen inside frozen is fine.
What we did: tested (1, [2, 3]).
Why this step? It contains a list , which is unhashable; that poisons the whole tuple → TypeError: unhashable type: 'list'.
Result: first two succeed as keys; the third raises TypeError.
Verify: build a dict with the first two keys (size 2), and confirm the third raises. ✅
Worked example 8 — Cell H: Word problem (pick the right structure)
Statement: You store the result of a dice roll pair so it can index a scoring table, e.g. the pair (3, 5). Should the pair be a tuple or a list, and what is board[(3,5)] if the table maps every ordered pair to its sum?
Forecast: Which structure, and what value comes out?
What we did: chose a tuple for the pair.
Why this step? A dice pair is a fixed record used as a dict key (Cell G says keys must be hashable). A list would raise TypeError. Meaning → tuple.
What we did: built board = {(a, b): a + b for a in range(1,7) for b in range(1,7)} and looked up (3, 5).
Why this step? The comprehension packs each (a, b) as an immutable key mapping to the sum — exactly what stable keys are for.
Result: board[(3, 5)] == 8, and the table has 6*6 = 36 entries.
Verify: 3 + 5 == 8 and len(board) == 36. ✅
Worked example 9 — Cell I: Exam twist —
count, index, and the ValueError
Statement: For t = (5, 3, 5, 8, 5), give t.count(5), t.index(8), and say what t.index(9) does.
Forecast: What happens when you ask for the index of a value that isn't there?
What we did: t.count(5) — count occurrences.
Why this step? count walks the whole tuple tallying matches; here 5 appears at positions 0, 2, 4 → three times.
What we did: t.index(8) — position of the first match.
Why this step? index returns the first matching position, counting from 0. 8 sits at slot 3.
What we did: t.index(9) — search for an absent value.
Why this step? These are the only two tuple methods, and index on a missing item cannot return a position, so it raises ValueError: tuple.index(x): x not in tuple (note: not TypeError).
Result: t.count(5) == 3, t.index(8) == 3, t.index(9) → ValueError.
Verify: the two numbers check out and the missing-value lookup raises ValueError. ✅
The two mistakes people confuse most are the write error and the count/search error. This flow separates them by cause.
Unpacked wrong number of names
Searched for a value not present
TypeError item assignment
ValueError not enough values
ValueError x not in tuple
Read it as: writing to a frozen tuple is a type sin (TypeError); counting/searching problems are value sins (ValueError). Cells E, B, and I each land on a different leaf.
Recall Predict the type / result
Is p = 3, 4 a tuple, and why? ::: Yes — the top-level comma builds a tuple; parentheses are optional.
a, *b, c = (1,2,3,4) — what is b? ::: The list [2, 3] (star target always yields a list).
x, y, z = (10, 20) raises what? ::: ValueError: not enough values to unpack — 3 names but 2 values.
t[0:2] on t = (1,2,3) returns what, and is t changed? ::: A new tuple (1, 2); t is unchanged (slice is read-only).
Can (1, [2]) be a dict key? ::: No — the inner list is unhashable, so the tuple is too.
t.index(9) when 9 is absent raises what? ::: ValueError, not TypeError.
Mnemonic The two-error rule
Slot write → TypeError. Wrong-count unpack or missing search → ValueError. Different sins, different alarms.
Tuples — immutability, use cases — the parent this drill serves.
Lists — mutability, methods — the mutable sibling from Cells B, D, F.
Dictionaries — keys and values — Cells G and H live here.
Sets — hashing & uniqueness — same hashability rule as dict keys.
Functions — return values — packing/unpacking of Cell A.
Mutable vs Immutable objects — the concept behind Cells E and F.
Hashing in Python — why Cell G behaves as it does.