datarekha
Python Easy Asked at AmazonAsked at MicrosoftAsked at IBM

In Python, do variables store values or references to objects?

The short answer

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 rests on one rule: every name is a pointer to an object on the heap. Assignment changes where a name points; it never copies the object. Hold that, and the rest of Python’s “why did my list change?” surprises dissolve.

It’s also why Python is best called pass by object reference, not pass-by-value or pass-by-reference: a function receives a copy of the pointer, so caller and function look at the same object until one of them reassigns its own pointer.

A worked example

a = [1, 2, 3]
b = a                  # b gets a COPY OF THE POINTER, not a copy of the list
print("Same object?", a is b)

b.append(4)            # mutates the one list both names point at
print("a after b.append:", a)

b = [9, 9]             # rebinds b to a NEW list; a is untouched
print("a after b = [9,9]:", a)
print("b now:", b)

# Immutable: rebinding looks like value semantics
x = 42
y = x
y += 1                 # builds a NEW int; x still points at 42
print("x:", x, " y:", y)

# Function arguments behave identically
def mutate(lst):
    lst.append("x")    # changes the caller's object
def rebind(lst):
    lst = []           # local name moves; caller's pointer doesn't

items = [1, 2]
mutate(items)
print("after mutate:", items)
rebind(items)
print("after rebind:", items)
Same object? True
a after b.append: [1, 2, 3, 4]
a after b = [9,9]: [1, 2, 3, 4]
b now: [9, 9]
x: 42  y: 43
after mutate: [1, 2, 'x']
after rebind: [1, 2, 'x']

The two telling lines are b.append(4) versus b = [9, 9]. The first mutated the shared list, so a became [1, 2, 3, 4]; the second rebound b to a brand-new list and left a exactly where it was. Same lesson inside a function: mutate reached the caller’s list through the shared pointer, while rebind only moved its own local name — so items stayed [1, 2, 'x'] after both calls.

The reference model as a picture

After  a = [1, 2, 3]  and  b = a:
  name "a" ──► list [1, 2, 3]   (one object on the heap)
  name "b" ──► (the same object)

After  b = [9, 9]:
  name "a" ──► list [1, 2, 3, 4]  (original object, mutated earlier)
  name "b" ──► list [9, 9]        (a new object)

To get a genuinely independent object, copy on purpose: b = a.copy() for a shallow copy, or import copy; b = copy.deepcopy(a) when the structure is nested.

Learn it properly The GIL

Keep practising

All Python questions

Explore further

Skip to content