How does variable assignment actually work in Python (references, not boxes)?

5 minintermediatefundamentalsreferencesmutability

Quick Answer

A Python variable is a name bound to an object, not a labeled memory box holding a value. x = [1, 2] makes the name x point at a list object; y = x makes y point at the same object — no copy happens. Reassigning x = something_else just repoints the name. It never affects y or the original object. Mutating through one name (x.append(3)) is visible through every other name bound to that same object.

Detailed Answer

Names are labels on objects, not boxes

x = [1, 2, 3]
y = x            # y now points at the SAME list object as x

y.append(4)
x                # [1, 2, 3, 4]  -- x sees the mutation too

y = [9, 9]       # rebinds y to a NEW list; x is untouched
x                # still [1, 2, 3, 4]

Think of x and y as sticky notes pointing at objects in memory, not as separate storage slots. y = x copies the pointer, not the object. Mutating the object through any name affects every name pointing at it. Reassigning a name just moves that one sticky note elsewhere.

Function arguments follow the same rule ("pass by object reference")

def add_item(lst):
    lst.append("x")       # mutates the caller's list — visible outside

def replace(lst):
    lst = ["new"]         # rebinds the LOCAL name lst; caller's list unaffected

data = [1, 2]
add_item(data)
data          # [1, 2, 'x']

replace(data)
data          # [1, 2, 'x']  -- unchanged; replace() only rebound its own local name

Python is neither "pass by value" nor "pass by reference" in the C++ sense. It's pass by object reference (sometimes called "call by sharing"): the function gets its own local name bound to the same object the caller passed. Mutating that object is visible to the caller; rebinding the local name is not.

Why id() and is make this concrete

x = [1, 2]
y = x
id(x) == id(y)   # True -- literally the same object
x is y            # True -- same thing, expressed with the `is` operator

id() returns the object's memory address (in CPython). Two names with the same id() are the same object, and mutating through one is always visible through the other.

Assignment binds a name to an object; it never copies the object. Multiple names can reference the same object, so mutating through one name is visible through all of them, while reassigning a name only changes what that one name points to. Function calls pass objects by reference-sharing: mutation is visible to the caller, rebinding the parameter name is not.