Which Python built-in types are mutable and which are immutable, and why does it matter?
Immutable types — int, float, bool, str, bytes, tuple, frozenset — cannot be changed after creation; operations return new objects. Mutable types — list, dict, set, bytearray — can be changed in place. Mutability determines hashability (only immutables can be dict keys/set members), function side-effect behaviour, and thread-safety considerations.
How to think about it
What the interviewer is checking
This question is a gateway to several deeper topics: “why can’t lists be dict keys?”, “why did my function modify the caller’s list?”, “why does x = x + 1 not mutate x?”. A strong answer covers the table, explains rebinding vs mutation, and demonstrates the function side-effect consequence.
Quick reference
| Type | Mutable | Hashable | Notes |
|---|---|---|---|
int, float, bool | No | Yes | Small ints cached by CPython |
str | No | Yes | Interned for many literals |
tuple | No | Yes (if all contents hashable) | Shallow immutability |
frozenset | No | Yes | Immutable counterpart to set |
bytes | No | Yes | |
list | Yes | No | |
dict | Yes | No | Keys must be hashable |
set | Yes | No | Elements must be hashable |
bytearray | Yes | No | Mutable counterpart to bytes |
Mutation vs rebinding — the most common confusion
Reassigning a variable is not mutation. It makes the name point to a different object while the original object is unchanged.
x = 5
x = 6 # rebinding — x now points to int 6; int 5 is unchanged
lst = [1, 2]
lst.append(3) # mutation — the same list object is modified in place
lst = [1, 2] # rebinding — lst now points to a new list object
Interactive demo — see mutation and rebinding side by side
The practical consequences
Hashability: Only immutable objects can be dict keys or set members because their hash must stay constant. A list key would let you change the key after insertion, breaking the hash table.
Function side effects: Pass a list to a function and the function can modify it in place; the caller sees the changes. Pass an int or str and the function can only rebind its local name — the caller’s variable is untouched.
frozenset — the immutable set
fs = frozenset({1, 2, 3})
hash(fs) # works
lookup = {frozenset({1, 2}): "pair", frozenset({3}): "single"}
lookup[frozenset({1, 2})] # "pair"
Use frozenset when you need a set as a dict key or as an element of another set.