Understanding Mutable and Immutable Objects
Mutable objects in Python are objects that can be changed after creation, like lists, dictionaries, and sets. Immutable objects cannot be changed after creation, like strings, integers, floats, and tuples.
The key difference is what happens when we try to modify them. With immutable objects, we do not actually change the value. A new object is created in memory instead. For example, if we define a variable x
equal to 5 and then perform x = x + 1
, the original integer has not been changed. A new integer object with value 6 has been created, and now x
points to this new object. The garbage collector will eventually clear the old integer object because no variable refers to it anymore.
With mutable objects, when we modify the content, the object stays in the same place in memory. If we have a list and append an item to it, the same list object is being modified, not creating a new one.
# Immutable objects - creates new objects
x = 5
print(id(x)) # Output: 140234567891234 (example ID)
x = x + 1
print(id(x)) # Output: 140234567891264 (different ID - new object!)
print(x) # Output: 6
# Mutable objects - modifies the same object
my_list = [1, 2, 3]
print(id(my_list)) # Output: 140234567892345
my_list.append(4)
print(id(my_list)) # Output: 140234567892345 (same ID - same object!)
print(my_list) # Output: [1, 2, 3, 4]
How Function Behavior Differs with Mutable and Immutable Objects
This distinction is important because it affects how objects behave when we pass them to functions. When we pass an immutable object to a function and modify it within that function, the modifications do not affect the original variable. When we pass a mutable object to a function and modify it within that function, the modifications do affect the original object.
# Function behavior with mutable vs immutable
def modify_immutable(x):
x = x + 1 # Creates new object
return x
def modify_mutable(lst):
lst.append(4) # Modifies same object
num = 5
result = modify_immutable(num)
print(num) # Output: 5 (unchanged)
print(result) # Output: 6
my_list = [1, 2, 3]
modify_mutable(my_list)
print(my_list) # Output: [1, 2, 3, 4] (changed!)
Understanding Hash Tables and Why Mutability Matters
To understand why mutability is so important in Python, we need to explore how dictionaries actually work internally. Python dictionaries use a data structure called a hash table, which relies on a fundamental principle: keys must never change.
What is a Hash Function?
A hash function is a mathematical function that takes any input—like a string or number—and converts it into a fixed-size number called a hash value. The key characteristic is that it is deterministic, meaning the same input always produces the same hash value. For example, "name" might always convert to 2834572, and "age" might convert to 9184736.
How Hash Tables Provide Fast Lookups
A hash table is a data structure that uses a hash function to map keys to specific memory locations. When we store a key like "name", Python converts it into a number that tells Python exactly where to store or find the value in memory. Instead of searching through items one by one, Python jumps directly to the right location.
# Dictionary
user_ages = {
'Alice': 25,
'Bob': 30,
'Charlie': 35
}
print(user_ages['Bob']) # Instant lookup, no matter how many users
# Behind the scenes (simplified):
# hash('Bob') -> 9184736 -> go directly to that memory location -> find 30
Python dictionaries and sets use hash tables internally. This is why dictionary lookups are so fast—O(1) on average—even with millions of items.
Why Keys Must Be Immutable
Keys must be immutable objects like strings, numbers, or tuples. If a key could change after being stored, its hash value would change too, and Python would not be able to find it anymore.
Since mutable objects can be modified after creation, their hash values are not stable. That is why we cannot use mutable objects—such as lists or dictionaries—as dictionary keys. Only immutable objects can serve as keys because their hash values remain constant throughout their lifetime.
# Why keys must be immutable
valid_key = ('latitude', 'longitude') # Tuple - immutable, works fine
coordinates = {valid_key: 'NYC'}
invalid_key = ['latitude', 'longitude'] # List - mutable
# locations = {invalid_key: 'NYC'} # TypeError: unhashable type: 'list'
# Valid - immutable keys
valid_dict = {
"name": "John", # String - immutable ✓
42: "answer", # Integer - immutable ✓
(1, 2): "tuple" # Tuple - immutable ✓
}
# Invalid - mutable keys
try:
invalid_dict = {[1, 2]: "list"} # List - mutable ✗
except TypeError as e:
print(e) # Output: unhashable type: 'list'
Key Takeaways
- Mutable objects can be changed after creation. When modified, the object in memory is altered, not replaced
- Immutable objects cannot be changed after creation. Attempts to modify them result in new objects being created
- Function parameters behave differently based on mutability. Mutable objects passed to functions can be modified externally; immutable objects cannot
- Hash functions take any input and convert it into a fixed-size number called a hash value in a deterministic way
- Hash tables use hash functions to map keys to memory locations, enabling very fast O(1) lookups
- Dictionary keys must be immutable because mutable objects have unstable hash values. If a key changed, its hash would change and Python could no longer find it
Top comments (0)