In Python, do variables store values or references to objects?
Python variables are names — they store references (pointers) to objects, not the objects themselves. Assignment binds a name to an object; it never copies the object. Understanding this explains why mutating an object through one name is visible through all other names that reference the same object.
How to think about it
Python’s object model has one central rule: every name is a pointer to an object on the heap. Assignment changes where the pointer points; it never copies the object.
This is why Python is sometimes described as “pass by object reference” rather than pass-by-value or pass-by-reference. The function receives a copy of the pointer — both caller and function look at the same object until one of them reassigns its pointer.
See it with id() and mutation
The reference model as a diagram
After: a = [1, 2, 3] and b = a
name "a" ──► list [1, 2, 3] (object on heap)
name "b" ──► (same object)
After: b = [9, 9]
name "a" ──► list [1, 2, 3, 4] (original object, mutated earlier)
name "b" ──► list [9, 9] (new object)
CPython interning — an implementation detail
CPython interns small integers (-5 to 256) and many string literals, so a = 1; b = 1; a is b is True. This is an optimisation, not a language guarantee — never use is to compare values; always use ==.
To get an independent copy: use b = a.copy() (shallow) or import copy; b = copy.deepcopy(a) (deep) for nested structures.