Skip to main content
CodeFlow
memory~14 minRoute 04 2 of 3

References vs Values

Why changing a variable in one place sometimes changes it in another.

Prerequisites:VariablesArrays & Lists

Story — name tags vs the things behind them

Imagine the variable xs isn't a thing, but a name tag pointing at a thing. When you write ys = xs, you don't copy what xs points at — you stick a second name tag on the same item. Two tags, one item. Mutate through one tag and both will "see" the change.

For numbers and strings (which most languages treat as immutable), you can never feel the difference: assignment looks like copying because there's nothing to mutate. For lists, dicts, objects, arrays — the kinds of things you actually edit — assignment is sharing. Knowing that is the difference between code that surprises you and code that doesn't.

Pointa says

The day you discover this is the day you stop asking "why did this variable change?" The two names had been pointing at the same underlying object the whole time.

See it — assignment, mutation, copy

Three operations to keep separate: assignment (ys = xs — share), mutation (ys.append(4) — touch the shared object), and copy (ys = xs[:] or list(xs) — duplicate the data so each name has its own). Mistake any of these and the bug shows up far from where you typed it.

✎ Sharpen your pencil

In one sentence, what does the "=" operator actually do for objects in Python?

Pointa says

When you "copy" an object in Python or Java, you usually copy the address, not the contents. Both names point at the same thing. Mutate one, the other "changes" too — because they're the same.

Try it — predict before you read

Exercise. In Python:

a = [1, 2]
b = [a, a]    # b is a list of two references to the SAME inner list
b[0].append(3)
print(b)
Reveal answer

[[1, 2, 3], [1, 2, 3]]. Both inner lists in b are the same object — a single reference appearing twice. Mutate through either, both "change."

Code it — five languages, two semantics

Python, JavaScript, Java pass objects by reference (assignment shares). C++ defaults to copy unless you explicitly take a reference (&) or pointer (*). Go is more nuanced — most types are values, but slices/maps share underlying storage.

# Numbers, strings, tuples — value-like (immutable)
a = 5
b = a
b += 1
print(a, b)              # 5 6 — independent

# Lists, dicts — reference-like (mutable, aliased)
xs = [1, 2, 3]
ys = xs                  # SAME list, two names
ys.append(4)
print(xs)                # [1, 2, 3, 4] — surprised?

# To copy, copy explicitly
zs = xs[:]               # or list(xs), or xs.copy()
zs.append(5)
print(xs)                # [1, 2, 3, 4] — untouched

Quiz it — make it stick

  1. Question 1

    In Python, why does mutating one list change another that was assigned from it?

  2. Predict — Question 2

    What does this Python print?

    def add(items, x):
        items.append(x)
    
    xs = [1, 2]
    add(xs, 3)
    print(xs)
  3. True or false — Question 3

    A deep copy and a shallow copy give the same result when copying a list of integers.

No Dumb Questions

Real questions other learners asked on this page.

  • Why does my function modifying a list affect the caller?
    Because the function received a reference to the same list. Passing a list "by value" would copy the entire list — slow and almost never what you want. To prevent mutation, copy explicitly (list[:], list.copy()) before passing.
  • What's a deep copy?
    Recursively copy every level — the outer list, the lists inside it, the dicts inside those. A shallow copy only duplicates the outer container; the inner objects stay shared. Use copy.deepcopy in Python; structuredClone in modern JS.
  • Why are integers in Python "value-like" if everything is a reference?
    Because integers are immutable. You can't mutate the number 5 — operations like x += 1 create a new int object and rebind the name. Immutability makes the reference vs value distinction invisible for those types.