Mutable objects in Python can be changed after they are created, and immutable objects cannot. Lists, dictionaries and sets are mutable. Numbers, strings, tuples and booleans are immutable. When you “change” an immutable value, Python does not alter it. It creates a new object and points your variable at it.
This sounds like theory, but it explains real bugs: why a function can change your list, why b = a does not make a copy, and why a default argument can remember its past. Every example below is run, and the output is real.
In this guide
The short version
- Mutable (can change in place):
list,dict,set, most classes you write. - Immutable (cannot):
int,float,bool,str,tuple,frozenset,None. b = anever copies. Both names point at the same object.- Only immutable objects (with immutable contents) can be dictionary keys or set items.
Variables are names, not boxes
In Python a variable is a name attached to an object, not a box that holds a value. Writing b = a attaches a second name to the same object. With a mutable object, a change made through one name is visible through the other:
a = [1, 2]
b = a
b.append(3)
print(a, a is b)
Output
[1, 2, 3] True
is asks “are these the very same object?”, and here it is True. With an immutable value the same assignment behaves differently, because you cannot change the object, only point a name at a new one:
x = 10
y = x
y += 1
print(x, y, x is y)
s = "hi"
t = s
t += "!"
print(s, t)
Output
10 11 False
hi hi!
When we did y += 1, Python made a new integer, 11, and pointed y at it. The original 10 and the name x were untouched. Strings behave the same way.
Which types are which
| Type | Mutable? | Example of changing it |
|---|---|---|
| list | Yes | items.append(3) |
| dict | Yes | d['key'] = 1 |
| set | Yes | s.add(3) |
| int, float, bool | No | x += 1 makes a new number |
| str | No | s += '!' makes a new string |
| tuple | No | t += (3,) makes a new tuple |
| frozenset, bytes, None | No | Cannot be changed |
| Your own class instances | Yes (by default) | obj.value = 5 |
Changing in place vs creating a new object
id() gives an object’s identity. If it stays the same after an operation, the object was changed in place. If it differs, a new object was created:
a = [1, 2]
before = id(a)
a.append(3)
print(id(a) == before)
a = a + [4]
print(id(a) == before)
print(a)
Output
True
False
[1, 2, 3, 4]
append() changed the existing list. But a = a + [4] built a brand new list and moved the name a to it. Any other name still attached to the old list would not see the change. That difference is the source of many surprises.
The += surprise
For lists, += changes the list in place. For tuples (and strings and numbers), it builds a new object. So the same-looking operator behaves differently, and it shows when a second name is involved:
a = [1, 2]
alias = a
a += [3]
print(alias)
t = (1, 2)
alias_t = t
t += (3,)
print(alias_t, t)
Output
[1, 2, 3]
(1, 2) (1, 2, 3)
The list changed for both names, because it is one list. The tuple’s other name kept the old value, because t += (3,) made a new tuple.
What happens when you pass them to functions
Python passes arguments by reference to the object. So a function that changes a mutable argument in place changes the caller’s object too, while the same code on an immutable argument only affects a local name:
def add_item(basket):
basket.append("pen")
def add_one(n):
n += 1
items = []
add_item(items)
print(items)
num = 5
add_one(num)
print(num)
Output
['pen']
5
The list got its "pen". The number stayed 5, because n += 1 inside the function created a new local integer.
There is one more trap here. Rebinding a name inside a function is not the same as changing the object:
def f(l):
l = l + [1]
def g(l):
l += [1]
nums = [0]
f(nums)
print(nums)
g(nums)
print(nums)
Output
[0]
[0, 1]
l = l + [1] built a new list and pointed the local name at it, so the caller’s list did not change. l += [1] modified the shared list in place, so it did. If you want a function to leave its argument alone, copy it first: l = list(l).
A tuple can contain mutable things
A tuple is immutable, meaning you cannot replace or add its items. But if one of those items is itself mutable, that item can still change:
t = ([1, 2], 3)
t[0].append(9)
print(t)
Output
([1, 2, 9], 3)
The tuple still points at the same list, and the list changed inside. Trying to assign to a position fails:
t = (1, 2)
try:
t[0] = 5
except TypeError as e:
print(type(e).__name__)
Output
TypeError
Why it matters for dictionary keys
Dictionary keys and set items must be hashable, which in practice means immutable. If a key could change, Python could never find it again. Try hashing a few objects:
for obj in [(1, 2), "text", 5, [1, 2], {"a": 1}]:
try:
hash(obj)
print(type(obj).__name__, "hashable")
except TypeError:
print(type(obj).__name__, "NOT hashable")
Output
tuple hashable
str hashable
int hashable
list NOT hashable
dict NOT hashable
This is why a tuple can be a dictionary key and a list cannot. More in Python Dictionaries Explained and Python List vs Tuple vs Set.
Common mistakes
Mistake 1: thinking b = a makes a copy
It does not. To get an independent list, copy it:
a = [1, 2]
b = a.copy()
b.append(3)
print(a, b)
Output
[1, 2] [1, 2, 3]
.copy() is a shallow copy: it copies the list but not the objects inside. For nested data you need a deep copy, covered in Shallow Copy vs Deep Copy in Python.
Mistake 2: multiplying a list of lists
[[0] * 2] * 3 does not make three separate rows. It makes one row and repeats a reference to it three times:
grid = [[0] * 2] * 3
grid[0][0] = 1
print(grid)
grid2 = [[0] * 2 for _ in range(3)]
grid2[0][0] = 1
print(grid2)
Output
[[1, 0], [1, 0], [1, 0]]
[[1, 0], [0, 0], [0, 0]]
Use a comprehension so each row is its own list.
Mistake 3: a mutable default argument
Default values are created once, so a list default is shared between calls. Use None and create the list inside the function. The full explanation is in Python Functions: Arguments, Return and Scope.
Try it yourself
Work out each answer first, then open the solution.
1. What does this print?
a = "cat"
b = a
a += "s"
print(b)
Show answer
Output
catStrings are immutable. a += "s" made a new string for a. The name b still points at "cat".
2. What does this print?
x = [1]
y = x
y.append(2)
print(x)
Show answer
Output
[1, 2]Both names point to one list, so appending through y shows up in x.
3. What does this print?
def f(l):
l.append("done")
l = ["new"]
data = []
f(data)
print(data)
Show answer
Output
['done']append changed the shared list. Then l = ["new"] only moved the local name l, which does not affect data.
4. What does this print?
a = (1, 2)
b = a
a += (3,)
print(a, b)
Show answer
Output
(1, 2, 3) (1, 2)Tuples are immutable, so a += (3,) built a new tuple. b still holds the original.
Run these in our free Python compiler to see it for yourself.
Frequently asked questions
What is the difference between mutable and immutable in Python?
A mutable object can be changed in place after it is created (lists, dicts, sets). An immutable object cannot (numbers, strings, tuples). To change an immutable value you create a new one.
Is a string mutable in Python?
No. Strings are immutable. Methods such as upper() and replace() return a new string and leave the original unchanged.
Is a tuple always immutable?
The tuple itself cannot be changed, but it can hold mutable objects such as lists, and those can still change. Only a tuple of immutable items is deeply unchangeable.
Why can’t a list be a dictionary key?
Keys must be hashable, and a list is mutable, so its hash could change. Use a tuple instead.
How do I copy a list without changing the original?
Use b = a.copy(), b = list(a) or b = a[:] for a shallow copy, and copy.deepcopy(a) when the list contains other mutable objects.
Are objects of my own classes mutable?
Yes, by default. You can change their attributes after creation. To make them unchangeable you have to design them that way, for example with a frozen dataclass.
Related reading
- Shallow Copy vs Deep Copy in Python – what to do about shared objects.
- Python Functions: Arguments, Return and Scope – including the mutable default trap.
- Python List vs Tuple vs Set: Key Differences – mutable and immutable collections side by side.
- 7 Python Gotchas That Trip Up Interviews – more surprises like these.
Run this code in your browser
The free Upskly compiler runs Python with nothing to install. Paste the example, change it and see what happens.
Stuck on a traceback?
AI Assist works inside the Python notebook, so you can ask about an error or a concept without leaving the cell.
Test yourself
Timed questions on Mutability, Copying and Identity, with an explanation for every answer.