Guide Python Intermediate

11.9 Immutability and Copying

Why tuples are immutable by design, the concrete benefits that follow -- thread safety, hashability, communicated intent -- why a tuple holding a mutable element isn't hashable, and why even an explicit copy() returns the same object.

3 min read

The general mutable-vs-immutable distinction is covered in 5.9 Mutable vs Immutable Types, and general copying mechanics in 6.5 Copying Objects. This page focuses on what immutability specifically buys tuples, and the one copying behavior that’s genuinely different from every mutable type.

Why Tuples Are Immutable

By design: a tuple represents a fixed, complete record — immutability is what guarantees that record can never be silently altered by code elsewhere that happens to hold a reference to it.

Benefits

  • Safe to share across functions/threads without defensive copying.
  • Hashable (if contents are hashable) — usable as a dict key or set member.
  • Communicates intent — signals to readers that this data is a fixed record, not a growing collection.
>>> t1 = (1, 2, 3)
>>> hash(t1)                # tuples of hashable items ARE hashable
529344067295497451
>>> d = {t1: "value"}       # usable as a dict key
>>> d
{(1, 2, 3): 'value'}

Limitations

A tuple can’t grow, shrink, or reorder in place — any “modification” requires building an entirely new tuple. A tuple containing a mutable element (like a list) is also not hashable, since its effective contents could still change (see 11.8 Nested Tuples).

>>> hash((1, [2, 3]))       # contains a list -- unhashable
Traceback (most recent call last):
TypeError: unhashable type: 'list'

Copying Tuples

b = a makes b reference the exact same tuple object as a — ordinary reference assignment, same as any other type.

>>> a = (1, 2, 3)
>>> b = a
>>> a is b
True

What’s different from lists: because a tuple can never be mutated, sharing a reference to it — via assignment or copy.copy() — is always completely safe. There’s no risk of one name’s changes surprising another, since neither can change anything.

>>> import copy
>>> c = copy.copy(a)
>>> c is a     # even an explicit copy returns the SAME object -- safe due to immutability
True

copy.deepcopy() still recursively copies a tuple that contains nested mutable objects, exactly as described in 6.5 Copying Objects — the shallow-copy optimization above only kicks in because a plain shallow copy of an immutable container has nothing to protect against.

Quick Interview Answer

“Immutability buys a tuple three concrete things: it’s safe to share across functions and threads with zero defensive copying, it’s hashable — as long as every element inside it is also hashable — which makes it usable as a dict key or set member, and it signals to a reader that this data is a fixed record. The copying behavior follows directly: since a tuple can never be mutated, even copy.copy() just hands back the identical object instead of duplicating anything, because there’s nothing to protect against by copying. copy.deepcopy() still does real work if the tuple holds nested mutable objects, since those inner objects genuinely could be mutated.”

Common Mistakes

  • Assuming any tuple is automatically hashable — it’s only hashable if every element inside it is hashable too; a tuple containing a list raises TypeError: unhashable type.
  • Expecting copy.copy() on a tuple to produce a genuinely separate object the way it does for a list — for an immutable object, CPython optimizes this to return the same object, which is observably correct behavior, not a bug.
  • Forgetting copy.deepcopy() still matters for a tuple that holds mutable elements — immutability at the tuple’s own level doesn’t make everything inside it independent.

Add More Questions to This Guide

Know a question that should be here? Share it and help the community!

Open Google Form