A deceptively simple puzzle that reveals one of Python's most fundamental concepts
The Challenge
Before we dive into any explanations, let's start with a quiz. Take a look at this Python code and predict what it will output:
a = 500
b = a
a = a + 100
list1 = [1, 2]
list2 = list1
list1.append(3)
print(f"b is {b}")
print(f"list2 is {list2}")
What do you think the output will be?
A) b is 600
and list2 is [1, 2]
B) b is 500
and list2 is [1, 2, 3]
C) b is 500
and list2 is [1, 2]
D) b is 600
and list2 is [1, 2, 3]
Take a moment to think through it. The code looks straightforward, but there's something subtle happening here that trips up even experienced developers.
Scroll down when you're ready for the answer...
The Answer
The correct answer is B: b is 500
and list2 is [1, 2, 3]
If you got it right, excellent! If not, don't worry—this behavior puzzles many Python developers. The key to understanding this lies in one crucial concept that many tutorials gloss over.
The Mystery Revealed
The different behaviors in our code aren't random or inconsistent. They're the logical result of how Python handles two different types of objects: mutable and immutable.
Let's break down exactly what's happening, step by step.
Part 1: The Integer Surprise
a = 500
b = a
a = a + 100
Here's what Python is actually doing:
-
a = 500
: Creates an integer object containing500
and assigns the namea
to point to it -
b = a
: Makesb
point to the same500
object thata
is pointing to -
a = a + 100
: This is the key line. It evaluatesa + 100
(which is600
), creates a brand new integer object containing600
, and reassignsa
to point to this new object
The crucial insight: integers are immutable in Python. You cannot change the value of an integer object once it's created. When you write a = a + 100
, Python doesn't modify the existing 500
object—it creates a new 600
object.
Since b
was never reassigned, it still points to the original 500
object. Hence: b is 500
.
Part 2: The List Plot Twist
list1 = [1, 2]
list2 = list1
list1.append(3)
Now let's trace through the list operations:
-
list1 = [1, 2]
: Creates a list object containing[1, 2]
and assignslist1
to point to it -
list2 = list1
: Makeslist2
point to the same list object thatlist1
is pointing to -
list1.append(3)
: Here's the difference—this modifies the existing list object by adding3
to it
The key insight: lists are mutable in Python. When you call append()
, you're modifying the existing list object, not creating a new one.
Since both list1
and list2
point to the same list object, and that object has been modified, both names now refer to [1, 2, 3]
. Hence: list2 is [1, 2, 3]
.
The Bigger Picture: Variables Are Names, Not Boxes
This puzzle illustrates a fundamental truth about Python that many developers misunderstand: variables are names that point to objects, not containers that hold values.
Think of it like this:
- A variable name is like a sticky note with a name written on it
- Objects are like boxes containing actual data
- Assignment sticks the name-tag onto a box
- Multiple names can point to the same box
When you understand this mental model, the mystery code makes perfect sense:
- With immutable objects (integers), operations create new boxes and move name-tags to point to them
- With mutable objects (lists), operations modify the contents of existing boxes while all name-tags pointing to that box stay put
Why This Matters
Understanding mutability isn't just academic—it prevents real bugs. Consider this common Python gotcha:
def add_item(item, target_list=[]):
target_list.append(item)
return target_list
# This doesn't work as expected!
list1 = add_item("first")
list2 = add_item("second") # Oops! list2 is now ["first", "second"]
The default list []
is mutable, so all function calls share the same list object. Once you understand object mutability, this behavior becomes predictable rather than mysterious.
Test Your Understanding
Now that you know the secret, try predicting the output of this code:
x = "hello"
y = x
x = x + " world"
dict1 = {"key": "value"}
dict2 = dict1
dict1["key"] = "modified"
print(f"y is '{y}'")
print(f"dict2 is {dict2}")
The answer: y is 'hello'
and dict2 is {'key': 'modified'}
(strings are immutable, dictionaries are mutable).
Wrapping Up
Python's variable system is elegant once you understand the underlying model. Variables don't contain objects—they point to them. Whether that pointed-to object can be modified depends on its mutability.
This single concept explains countless Python behaviors that seem mysterious at first glance. Master it, and you'll find Python code much more predictable and debuggable.
Understanding mutability is just one of many elegant concepts that make Python powerful. Once you see the pattern, you'll start recognizing it everywhere in your code.
What other Python mysteries have puzzled you? Share your examples in the comments—there might be a simpler explanation than you think! And if you found this helpful, follow for more Python insights that demystify the magic behind the code.
Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like Genius.
Top comments (0)