When I first started coding in Python, I couldn’t figure out why one piece of code changed my data unexpectedly while another stayed untouched. If you’ve ever been surprised by your variables behaving like they have a “mind of their own,” you should understand one of the most important concepts in Python: mutable vs immutable objects.
This concept isn’t just theoretical—it directly impacts how your programs behave, how memory is managed, and how bugs sneak in. Once I understood it deeply, I started writing much cleaner, bug-free Python code.
What Are Mutable and Immutable Objects?
- Immutable objects: Once created, they cannot be changed. Any operation that looks like “modifying” them actually creates a new object in memory.
- Mutable objects: They can be modified in place, meaning you can change their content without changing their identity.
A quick test in Python is to use the built-in id() function. It shows the memory identity of an object. Memory identity means the unique “address tag” of an object stored in memory.
x = 10
print(id(x)) # Output: 140715649812048
x += 1
print(id(x)) # Different ID, because integers are immutable
Even though you “changed” x, Python created a new integer object under the hood.
But watch what happens with a list:
my_list = [1, 2, 3]
print(id(my_list)) # Output: 2323423432
my_list.append(4)
print(id(my_list)) # Same ID, because lists are mutable
The list changed in place. Same object, but with new content.
Immutable Objects in Python
Common immutable types:
intfloatstrtuplefrozensetbytes
Strings Are Immutable
name = "henry"
print(id(name)) # Output: 1871131565040
name += "chan.tech"
print(id(name)) # Output: 1871162611888
Even though it feels like we added to the string, Python created a new string object. That’s why string concatenation in a loop is slow—it keeps making new copies.
Tuples Are Immutable
t = (1, 2, 3)
# t[0] = 10 # TypeError: 'tuple' object does not support item assignment
But here’s the tricky part: a tuple can contain mutable objects. When you put a mutable object (like a list or dictionary) inside a tuple, the tuple is storing a reference to that object, not a frozen copy.
t = ([1, 2], 3)
print(id(t)) # Output: 1871162601728
t[0].append(4)
print(id(t)) # Output: 1871162601728
print(t) # Output: ([1, 2, 4], 3)
So, while the tuple itself is immutable, the objects inside it can still change.
Mutable Objects in Python
Common mutable types:
listdictsetbytearray
Lists Are Mutable
numbers = [1, 2, 3]
print(id(numbers))
numbers.append(4)
print(numbers) # Output: [1, 2, 3, 4]
print(id(numbers)) # Same ID
The same list object got updated.
Dictionaries Are Mutable
person = {"name": "Henry", "age": 30}
print(id(person))
person["age"] = 31
print(person) # Output: {'name': 'Henry', 'age': 31}
print(id(person)) # Same ID
This makes dictionaries powerful but also dangerous if you accidentally modify shared references.
Passing by Reference vs Passing by Value in Python
def update_list(items):
items.append(4)
nums = [1, 2, 3]
update_list(nums)
print(nums) # Output: [1, 2, 3, 4]
Why did nums change even though we didn’t return anything? Because lists are mutable, the function updated the original list in place.
If you don’t want side effects, pass a copy of mutable objects.
update_list(nums.copy()) # nums remains unchangedHow about passing a immutable object into a function?
def update_number(n):
n += 1
x = 10
update_number(x)
print(x) # Output: 10
x didn’t change because integers are immutable.
Performance Implications
Immutable objects: Safe, hashable (can be used as dictionary keys), but can be memory-expensive if modified frequently.
Mutable objects: Efficient for frequent updates, but can introduce hard-to-find bugs if accidentally shared across functions or modules.
def add_item(item, items=[]):
items.append(item)
return items
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] <- Unexpected!
def add_item_2(item, items=None):
if items is None:
items = []
items.append(item)
return items
print(add_item_2(1)) # [1]
print(add_item_2(2)) # [2]When to Use Immutable vs Mutable?
- Use Immutable Objects When:
- You need reliable dictionary keys or set elements.
- You want data safety across multiple functions.
- You don’t want unexpected side effects.
- Use Mutable Objects When:
- You need efficient in-place updates.
- You’re working with large data collections that change often.
- Performance is a concern and immutability would cause too many object copies.
