Python
tuples
mutable items
immutable
data structures

Why can tuples contain mutable items?

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Python tuples are described as immutable, yet you can put a list inside a tuple and then modify that list. This surprises many developers the first time they encounter it. The behavior makes perfect sense once you understand what "immutable" actually means for a tuple. This article explains the distinction between an object's identity and its value, why tuples store references rather than copies, and the practical consequences of putting mutable objects inside tuples.

What Immutability Really Means for Tuples

When Python says a tuple is immutable, it means that the tuple's structure cannot change after creation. You cannot add elements, remove elements, or reassign a slot to point to a different object. However, immutability applies to the tuple itself, not to the objects it contains.

A tuple holds references (pointers) to objects. Immutability guarantees that those references will never change. The reference in slot 0 will always point to the same object. But if that object happens to be mutable, the object itself can still change through its own methods.

python
1my_tuple = ([1, 2, 3], "hello")
2
3# The list inside the tuple can be modified
4my_tuple[0].append(4)
5print(my_tuple)  # ([1, 2, 3, 4], 'hello')
6
7# But you cannot reassign the tuple's slot to a different object
8# my_tuple[0] = [5, 6]  # TypeError: 'tuple' object does not support item assignment

The tuple does not own a copy of the list. It holds a reference to the original list object. The list continues to live its own life, and the tuple simply points to it.

Identity vs. Value

Python distinguishes between an object's identity (its memory address, checked with is) and its value (checked with ==). Immutability for a tuple means the identities of its elements are fixed. The values of those elements can change if the elements themselves are mutable.

python
1my_list = [1, 2]
2my_tuple = (my_list,)
3
4print(id(my_tuple[0]))  # e.g., 140234567890
5my_list.append(3)
6print(id(my_tuple[0]))  # Same id: 140234567890
7print(my_tuple)          # ([1, 2, 3],)

The identity of my_tuple[0] never changed. It still refers to the exact same list object. The list's contents changed, but the tuple's reference did not.

Why Python Allows This

Python could, in theory, perform a deep copy of every object placed into a tuple, making the contents truly frozen. But that would be expensive and would break the reference semantics that Python relies on everywhere.

Consider a tuple used as a lightweight record to group related data. If the tuple deep-copied everything, modifying the original list would not update the tuple's version, which would be confusing in a different way. Python chose consistency: all containers store references, and immutability only applies to the container's own structure.

The Hashability Consequence

One practical consequence is that tuples containing mutable items are not hashable. Python requires that hashable objects have a hash value that never changes during their lifetime. Since a list inside a tuple can change, the tuple's effective value can change, which would violate the hash contract.

python
1hashable_tuple = (1, 2, "three")
2print(hash(hashable_tuple))  # Works fine
3
4unhashable_tuple = (1, [2, 3])
5# hash(unhashable_tuple)  # TypeError: unhashable type: 'list'

This means you cannot use a tuple containing a list as a dictionary key or add it to a set.

python
1# This works
2valid_key = (1, 2, 3)
3d = {valid_key: "value"}
4
5# This raises TypeError
6# invalid_key = (1, [2, 3])
7# d = {invalid_key: "value"}

Augmented Assignment Surprise

One particularly tricky edge case involves the += operator on a list inside a tuple.

python
1t = ([1, 2],)
2try:
3    t[0] += [3, 4]
4except TypeError as e:
5    print(e)  # 'tuple' object does not support item assignment
6
7print(t)  # ([1, 2, 3, 4],)

The list actually gets modified because += calls list.extend() first, which succeeds. Then Python tries to assign the result back to t[0], which fails because tuples do not support item assignment. So you end up with a modified list and a raised exception. This is a well-known Python gotcha documented in the official FAQ.

Common Pitfalls

Assuming tuples are deeply immutable. Tuples guarantee structural immutability only. If you need a fully frozen data structure, use a tuple of immutable elements (strings, numbers, other tuples) or convert mutable items with tuple() or frozenset().

Using tuples with mutable elements as dict keys. This will raise a TypeError at runtime. Always verify that all elements in a tuple are hashable before using the tuple as a key.

Relying on copy behavior. Placing a list in a tuple does not create a copy. If you modify the original list variable, the change is visible through the tuple as well. If you need isolation, pass list(original) to create a shallow copy.

Confusing += with .append(). Use t[0].append(value) to modify a list inside a tuple safely. The += operator triggers an assignment step that raises a TypeError, even though the underlying list mutation still occurs.

Summary

Tuples are immutable in the sense that their structure, the sequence of references they hold, is fixed after creation. However, the objects those references point to can be mutable and can change independently. This is because Python containers store references, not copies. The practical upshot is that a tuple containing a mutable item like a list is not hashable and cannot serve as a dictionary key or set member. If you need a truly frozen container, ensure every element inside the tuple is itself immutable.


Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.