Python Mutable vs Immutable Objects

·

·

Mutable vs Immutable Objects

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:

  • int
  • float
  • str
  • tuple
  • frozenset
  • bytes

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:

  • list
  • dict
  • set
  • bytearray

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 unchanged

How 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?

  1. 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.
  2. 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.