Mutability and References
A variable holds a reference to a value, not a private copy — and for a mutable value like a list, that means two names can see the same change.
By the end of this lesson, you can
- Explain the difference between a mutable value, like a list, and an immutable one, like a number
- Predict when two variable names refer to the same mutable object, and when changing one affects the other
- Explain why passing a list to a function can let that function change the original, unlike passing a number
Why it matters
The very first lesson in this course established that a variable is a name that refers to a value. What it didn’t cover: some values can be changed in place, and some can’t — and for the ones that can, two different names can end up referring to the exact same one. Missing this is how a change made through one variable “mysteriously” shows up somewhere else that never seemed to touch it.
Mental model
An integer, like 5, is immutable — it can never change. “Changing”
a variable that holds one really means making the name refer to a
different value entirely. A list is mutable — the same list
object can have its contents changed without becoming a different
object.
a = [1, 2, 3]
b = a
b.append(4)
print(a)
print(b)[1, 2, 3, 4]
[1, 2, 3, 4]b = a didn’t copy the list — it made b a second name for the exact
same list a already referred to. b.append(4) mutates that one
shared list, so both names see the change, because there was only ever
one list.
Compare that to numbers:
x = 5
y = x
y = y + 1
print(x)
print(y)5
6y = y + 1 doesn’t change the number 5 — it can’t be changed, it’s
immutable. Instead, y gets reassigned to refer to the new value 6,
leaving x still pointing at 5, completely unaffected.
Trace it
| Code | What actually happens |
|---|---|
| a = [1, 2, 3] | a refers to a new list object |
| b = a | b refers to the same list object as a — not a copy |
| b.append(4) | the one shared list is mutated; both a and b see [1, 2, 3, 4] |
| x = 5; y = x | y refers to the same immutable 5 — but that's fine, since it can never change |
| y = y + 1 | y now refers to a new value, 6; x still refers to the original, unaffected 5 |
Functions and mutable arguments
The same rule applies when a list is passed into a function:
def add_item(items):
items.append("new")
my_list = ["a", "b"]
add_item(my_list)
print(my_list)['a', 'b', 'new']items inside the function is just another name for the same list
my_list refers to outside it — items.append(...) mutates that
shared list, so the caller sees the change too, even though my_list
was never reassigned. A number argument behaves completely differently:
def increment(n):
n = n + 1
my_number = 5
increment(my_number)
print(my_number)5n = n + 1 reassigns the local name n — it never touches
my_number, because numbers can’t be mutated in place at all.
Check your understanding
Or reveal the answer without checking
Answer:["apple", "banana"]
backup = cart makes backup a second name for the same list cart refers to, not a copy. Appending through cart mutates the one shared list, so backup sees "banana" too.
Or reveal the answer without checking
Answer:It changes too, since the parameter is another name for the same list object
Passing a list doesn't copy it — the parameter refers to the same list object the caller's variable does, so mutating it inside the function is visible outside too.
Practice: warm-up
Trace this program — write down what each print shows.
original = [10, 20]
copy_name = original
original.append(30)
print(copy_name)
count = 1
other_count = count
count = count + 10
print(other_count)
Stuck? Reveal one hint at a time.
Hint 1
copy_name = original does not make a copy — check what it actually does.
Hint 2
count is a number, so count = count + 10 works completely differently than original.append(30) does.
Reveal the trace
Try the problem yourself before reading this. There is often more than one reasonable approach — treat this as one worked example, not the only correct answer.
copy_name = original -> same list, not a copy
original.append(30) -> mutates the one shared list
print(copy_name) -> [10, 20, 30] — sees the change
other_count = count -> other_count refers to the same 1, but numbers can't mutate
count = count + 10 -> count now refers to a new value, 11
print(other_count) -> 1 — completely unaffectedPractice: apply it
This function is supposed to return a sorted copy of a list without changing the original, but the original gets changed anyway:
def get_sorted(numbers):
numbers.sort()
return numbers
scores = [30, 10, 20]
sorted_scores = get_sorted(scores)
print(scores)
Or reveal the answer without checking
Answer:[10, 20, 30] — changed, because numbers inside the function is the same list object as scores, and .sort() mutates it in place
numbers is just another name for the same list scores refers to — calling .sort() on it mutates that one shared list, so the caller's scores ends up sorted too, not just the returned value.
Modification challenge: fix get_sorted so it doesn’t change the
caller’s original list — inside the function, make a copy first with
numbers = numbers.copy() before sorting, so the sort only affects that
new, independent list.
Summary
- An immutable value (a number, a string) can never change — reassignment always makes a name refer to a new value instead.
- A mutable value (a list) can be changed in place — the same object, with different contents.
b = anever copies — it makesba second name for whateveraalready refers to. For a mutable value, mutating through one name is visible through every other name pointing at the same object.- Passing a mutable value into a function behaves the same way: the parameter is another name for the caller’s object, so mutating it inside the function is visible outside too.