What's the mutable default argument trap, and how do mutable vs immutable types cause it?
Quick Answer
Default argument values are evaluated **once**, when the `def` statement runs, not on every call. If the default is a mutable object (a list, dict, or set), every call that doesn't pass that argument **shares and mutates the same object**, causing state to leak across calls. Fix it by defaulting to `None` and creating the mutable object inside the function body.
Detailed Answer
The trap
def add_item(item, bucket=[]):
bucket.append(item)
return bucket
add_item("a") # ['a']
add_item("b") # ['a', 'b'] -- surprise! same list as before
The default [] is created once, at function-definition time, and
stored on the function object (add_item.__defaults__). Every call that
omits bucket reuses that exact same list, so mutations accumulate across
unrelated calls.
Why immutable defaults don't have this problem
def greet(name, suffix="!"):
return name + suffix
"!" is immutable — nothing inside greet can mutate the string object
itself, so there's no shared, mutable state to leak. The bug is specific to
mutable default values (list, dict, set, or any mutable custom
object).
The fix
def add_item(item, bucket=None):
if bucket is None:
bucket = []
bucket.append(item)
return bucket
Now a fresh list is created on every call that doesn't supply bucket,
while callers who do want to accumulate into a shared list can still pass
one explicitly.
The general lesson: mutable vs immutable
- Immutable (
int,float,str,tuple,frozenset,bytes): any "modification" creates a new object; the original is never changed. Safe to share across function calls, default arguments, and dict keys. - Mutable (
list,dict,set, most custom classes): the object can be changed in place; sharing a reference means all holders see the mutation. Never use a mutable object as a default argument, and be careful when a mutable object is a class attribute (shared across all instances) instead of an instance attribute (set in__init__).
class Bad:
items = [] # class attribute — shared by every instance!
def __init__(self):
pass
class Good:
def __init__(self):
self.items = [] # instance attribute — one per object
Interview-ready summary: Default arguments are evaluated once at
def-time and stored on the function object, so a mutable default is
shared across every call that uses it. Always default mutable arguments to
None and construct the real object inside the function body — and apply
the same caution to mutable class attributes.