Why Python Developers Fail Default Mutable Argument Questions in Interviews
There is a Python behavior that has ended more technical interviews than almost any other single concept. It appears in entry-level screens at startups and senior-level loops at FAANG companies alike.
It is the default mutable argument trap.
Here is the classic version:
def add_item(item, items=[]):
items.append(item)
return items
print(add_item("a"))
print(add_item("b"))
print(add_item("c"))
Before reading further, write down what you think this prints.
Most people write:
['a']
['b']
['c']
The actual output is:
['a']
['a', 'b']
['a', 'b', 'c']
If you got it wrong, you are in good company. If you got it right, this article explains why it works this way and what variations interviewers use to trip up people who know the basic version.
Why This Happens
In Python, default argument values are evaluated once when the function is defined, not each time the function is called.
That single sentence is the entire explanation. The list [] in def add_item(item, items=[]) is created once when Python parses the function definition. It is stored as part of the function object itself. Every call to add_item that does not pass an explicit items argument shares that same list object.
You can see this yourself by checking the function's internal attributes:
print(add_item.__defaults__)
# After three calls, this outputs: (['a', 'b', 'c'],)
The default value lives inside the function and mutates with each call.
The Trace Table for This Problem
A trace table helps visualize how the state changes across executions.
| Call | item | items (default object) | After append | Returns |
|---|---|---|---|---|
add_item("a") |
"a" | [] |
["a"] |
["a"] |
add_item("b") |
"b" | ["a"] |
["a", "b"] |
["a", "b"] |
add_item("c") |
"c" | ["a", "b"] |
["a", "b", "c"] |
["a", "b", "c"] |
The key column is the third one. The default object does not reset between calls. It carries its state forward.
How Interviewers Make It Harder
Once you know the basic trap, interviewers add layers. Here are the three most common variations:
Variation 1: The dictionary default
Same behavior, different data structure.
def update_config(key, value, config={}):
config[key] = value
return config
print(update_config("debug", True))
print(update_config("verbose", False))
Output:
{'debug': True}
{'debug': True, 'verbose': False}
Variation 2: The conditional write
The second call does not duplicate because of the conditional check, but the underlying list still persists.
def add_if_missing(item, items=[]):
if item not in items:
items.append(item)
return items
print(add_if_missing("x"))
print(add_if_missing("x"))
print(add_if_missing("y"))
Output:
['x']
['x']
['x', 'y']
Variation 3: The None pattern fix
Interviewers sometimes show you the correct version and ask you to explain exactly why it works:
def add_item_fixed(item, items=None):
if items is None:
items = []
items.append(item)
return items
Here, None is immutable. A new, clean list is created inside the function body on each call where items is not provided. The default reference itself never mutates.
What the Interviewer Is Actually Testing
When an interviewer asks a default mutable argument question, they are not testing whether you memorized this specific quirk. They are testing three deeper things:
Interpreter Mechanics: Whether you understand that Python evaluates defaults at definition time, not call time. This requires understanding how the Python interpreter parses and stores function objects.
State Tracking: Whether you can trace through the execution manually without running the code. The ability to predict output by reasoning about state is a fundamental skill that transfers to debugging, code review, and system design.
Best Practices: Whether you know the correct pattern to avoid the bug. The
Nonesentinel pattern is the accepted industry solution. Knowing it exists and explaining why it works demonstrates depth beyond just knowing the trap exists.
How to Practice This Type of Problem
The most effective practice for mutable default argument questions and similar Python traps is dry-run tracing. Reading the code line by line, tracking every variable state in a table, and predicting the output before running it.
I built a free tool called PyCodeIt that generates unique Python tracing problems across this exact category of issues. Each problem is AI-generated so you never see the same question twice, and the explanation after each answer shows you the complete variable trace.
You can try it out at pycodeit.com without creating an account.
Three More Mutable Trap Patterns to Study
If you want to master pythonic edge cases, make sure to look into these next:
Class-level mutable attributes shared across instances
The
+=operator on lists inside functions (modifies in place versus rebinding)Generator expressions that close over loop variables
Each of these works on the same underlying principle as the default mutable argument. Once you understand that Python names are references to objects rather than containers holding values, all of these patterns become completely predictable.
Written by the developer behind PyCodeIt, a free AI-powered Python dry-run practice platform for technical interview preparation.
Top comments (0)