How does variable assignment actually work in Python (references, not boxes)?
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.