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.

Interview-ready summary: 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.