PYTHON & CODING · QUESTION 76
Why are mutable default arguments risky in Python?
On this page
Python evaluates default argument expressions once when a function is defined, not each time it is called. If a default is a mutable object such as a list, calls can share and modify the same object unexpectedly. Use `None` as the default and create a fresh list or dictionary inside the function when no value is supplied.
LET'S MAKE IT SIMPLE
First, the idea in plain English
A function definition is created once and its default values stay attached to it. A list can be changed, so if the same default list is reused, one call may affect later calls. This surprises people who expect a new empty list every time the function runs.
A USEFUL COMPARISON
Picture it this way
Imagine a shared whiteboard used as the “default notes” for every meeting. Changes from one meeting remain for the next. Creating a fresh board inside each meeting avoids the shared state; Python’s default mutable object behaves like the shared board.
FOLLOW THE JOURNEY
A mutable default object is shared across calls
Compare definition-time default creation with per-call initialization.
This simplified visual highlights the main relationships. In practice, the exact design depends on the requirements; Each call then gets independent state.
A function definition stores one default list that multiple calls mutate, while a None sentinel creates a fresh list inside each call.
How to read this visual
This simplified visual highlights the main relationships. In practice, the exact design depends on the requirements; Each call then gets independent state.
UNDER THE HOOD
How it works, step by step
Define function
Python evaluates the default expression when it executes the definition.
Why it matters: The object becomes part of the function’s stored defaults.
Call without argument
The function receives the same stored object.
Why it matters: A mutable value can retain earlier changes.
Initialize per call
Use None and create a new collection inside the function.
Why it matters: Each call then gets independent state.
OUTSIDE THE TEXTBOOK
A practical example
A helper `append_item(item, items=[])` appends to one shared list, so a later call without `items` sees previous items. Define `items=None`, then assign `items = [] if items is None else items` inside the function to make the default behavior fresh.
GO A LITTLE DEEPER
Technical details that matter
- Default expressions are evaluated in the defining scope at function-definition time.
- Immutable defaults such as integers and strings do not have the same mutation issue, though mutable state can still be nested inside custom objects.
- Intentional shared caches can use mutable defaults, but explicit state or a cache decorator is clearer.
def append_item(item, items=None):
if items is None:
items = []
items.append(item)
return itemsThe None sentinel distinguishes “no list supplied” from an explicitly supplied list. A new list is allocated only for calls that omit the argument.
MAKE A GOOD CHOICE
When is this useful?
- Writing helpers that accept optional collections.
- Reviewing unexpected state leaking between function calls.
- Understanding function objects and definition-time evaluation.
Use a factory or explicit object when the function needs intentional persistent state. Avoid mutable defaults for per-call data.
WATCH OUT FOR
Common misunderstandings
- Using `[]` or `{}` as a default and mutating it.
- Replacing an explicitly supplied empty list because it is falsey.
- Assuming default expressions run on every call.
REAL-WORLD TRADE-OFFS
Limitations and failure cases
- The None pattern requires a sentinel if None is also a meaningful explicit value.
- Hidden state may still be introduced through globals or object attributes.
WHAT TO REMEMBER
Key takeaways
- 1Defaults are evaluated once when the function is defined.
- 2Mutable defaults can persist mutations between calls.
- 3Use a sentinel and create fresh mutable state inside the function.
PRACTICE THE FOLLOW-UP
What might an interviewer ask next?
Is an empty tuple safe as a default?
A tuple itself is immutable, so it cannot be appended to. But an immutable tuple containing mutable objects can still expose shared mutable state.
Why not use `items = items or []`?
That replaces any explicitly supplied empty list because it is falsey. Checking `items is None` preserves the caller’s object.
SAY IT OUT LOUD
How to explain this in an interview
“Python evaluates default arguments once, when the function is defined. If that default is a list and the function mutates it, later calls share the changes. I use None as a sentinel and create a fresh list inside the function when needed. I check with `is None` so an explicitly supplied empty list remains intact.”
Connect this question to the roadmap topic it supports.