Q1Which of these groups contains only mutable types?
Mutable and Immutable Types
Learn the difference between mutable and immutable types in Python and dodge the shared-reference bug right in your browser.
Why you need to learn this
This article isn't for complete beginners, but the topic is unavoidable if you want a deep understanding of programming. It's a common story for learners to hit a mutability bug and spend hours debugging it.
In Python, you might think y = x just connects two variables with an equals sign, only to find that editing one rewrites the other too. That's mutability.
This chapter nails down the difference between "mutable" (changeable) and "immutable" (unchangeable), and shows you how to copy safely.
A type that lets you rewrite the variable itself (via append, element assignment, update, and the like) is mutable; one that doesn't is immutable.
list / dict / set are mutable, and everything else (int / float / str / bool / tuple) is immutable.
Immutable types — changing one doesn't affect the other
With an immutable type (an unchangeable type), once you've passed a value with y = x, changing x later has no effect on y.
For example, after x += 1, x becomes 11, but y stays at the original 10.
# int is immutable
x = 10
y = x
x += 1
print(x) # 11
print(y) # 10 <- unchanged
# str is immutable too
x = "hello"
y = x
x = x + " world"
print(x) # hello world
print(y) # hello
# tuple is immutable too
x = ("a", "b")
y = x
x = ("c", "d")
print(x) # ('c', 'd')
print(y) # ('a', 'b')
Python variables are "name tags on boxes"
A Python variable doesn't hold the value itself — it's more like a name tag pointing to data (a box) in memory. When you write y = x, Python doesn't make a new box; it sticks another name tag (y) on the same box that x points to.
- Immutable types: rebinding like x = 11 just moves x's name tag onto a different box. y still points to the original box, so its value stays independent.
- Mutable types: an in-place operation like x.append(...) mutates the shared box itself, so the change is visible through y too.
This is the line that splits immutable from mutable.
Mutable types — y = x makes changing one change the other
With a mutable type (list / dict / set), though, writing y = x leaves y and x pointing at the same box.
If you then do something that rewrites the contents, like x.append(...), the change shows up through y too.
y = x doesn't create a separate copy — both names point at the same place.
Operations that rewrite the contents, like append, change the shared box both name tags refer to, so the change is visible through y as well.
# list is mutable
x = ["a", "b"]
y = x # just adds another name tag y to the same list
x.append("c") # rewrites the contents directly
print(x) # ['a', 'b', 'c']
print(y) # ['a', 'b', 'c'] <- y grew too!
# remove behaves the same way
x.remove("b")
print(y) # ['a', 'c'] <- disappears from y too
# dict and set show the same behavior
d = {"k": 1}
e = d
e["new"] = 99
print(d) # {'k': 1, 'new': 99} <- d grew too
Why is sharing the default, anyway?
If y = x copied the whole contents every time, then when, say, x holds millions of records fetched from a database, memory and runtime would balloon fast.
So in Python, a variable name isn't the value itself — it's a name tag pointing at a location in memory.
You can't change this mechanism, so it's on you (the writer) to use it carefully.
Use copy() to get an independent version
When you want to keep the original data and create a separate version, use the copy() method.
Writing y = x.copy() copies the contents into a new box and hands that to y, so changes to x afterwards don't affect y.
y = x.copy() allocates a new region of memory at that moment.
After that, rewriting x's contents with x.append(...) or similar has no effect on y.
# list / dict / set all have .copy()
x = ["apple", "lemon"]
y = x.copy()
x.append("grape")
print(x) # ['apple', 'lemon', 'grape']
print(y) # ['apple', 'lemon'] <- not affected
# dict.copy() works the same
d = {"a": 1, "b": 2}
e = d.copy()
d["c"] = 3
print(d) # {'a': 1, 'b': 2, 'c': 3}
print(e) # {'a': 1, 'b': 2} <- not affected
# set.copy() works the same
s = {1, 2}
t = s.copy()
s.add(3)
print(s) # {1, 2, 3}
print(t) # {1, 2} <- not affected
# list has a few other ways to copy
x = ["apple", "lemon"]
y1 = list(x) # passing through the constructor gives a new list
y2 = x[:] # full slice copy (from start to end)
Watch out when your list contains other lists
copy() only builds a new outer box. Mutable values inside it (like lists inside a list) stay shared. This is called a shallow copy.
Be careful with nested data.
In this article you learned the difference between mutable and immutable types, and how copy() gives you an independent version.
The idea that a variable name isn't the value itself but a name tag pointing at a location in memory carries over to other languages too, not just Python. When you work with mutable types, slip in a copy().
Knowledge Check
Answer each question one by one.
Q2What is the value of b after running the following code?
``
a = [1, 2, 3]
b = a
a.append(4)
Q3You want to keep the contents of the original list a and get an independent version b. Which is the most appropriate way to write it?