Python’s Most Misunderstood Feature: Mutable Defaults
Understanding mutable default arguments isn’t just for writing cleaner code — it’s key to avoiding hidden bugs and mysterious behavior in…

Understanding mutable default arguments isn’t just for writing cleaner code — it’s key to avoiding hidden bugs and mysterious behavior in your Python functions.
If you’ve ever found yourself baffled by why a Python function is returning unexpected results — especially when default arguments are involved — you’re not alone.
There’s one quirky feature in Python that trips up even experienced developers: mutable default arguments.
It’s subtle, counterintuitive, and can lead to bugs that are notoriously hard to track down.
Let’s dive into why this happens, what it really means, and most importantly, how you can avoid it.
The Unexpected Behavior
Consider this simple Python function:
def add_item(item, item_list=[]):
item_list.append(item)
return item_list
Now try calling it a few times:
print(add_item('apple')) # ['apple']
print(add_item('banana')) # ['apple', 'banana']
print(add_item('cherry')) # ['apple', 'banana', 'cherry']
Wait… what?
Shouldn’t the function return a new list each time?
That’s the common assumption — and it’s wrong.
What’s Going On Under the Hood?
The confusion stems from how default arguments are evaluated in Python.
In Python, default parameter values are evaluated only once — at the time the function is defined, not each time it’s called.
So in our example:
def add_item(item, item_list=[]):
The item_list=[]
part creates a list once, and that same list is reused on every call where item_list
isn’t explicitly provided.
Why This Is Dangerous
The reason this behavior is so misleading is because most developers expect a new list to be created each time the function is called — as it would be in many other programming languages.
Instead, the default list keeps growing with each call. If you’re writing functions that are meant to be stateless or pure, this can introduce bugs that silently compound over time.
It’s not limited to lists either. Any mutable object — like dictionaries or sets — will behave the same way.
The Correct Way: Use None
as a Sentinel
The fix is surprisingly simple and Pythonic:
def add_item(item, item_list=None):
if item_list is None:
item_list = []
item_list.append(item)
return item_list
Now the output is as expected:
print(add_item('apple')) # ['apple']
print(add_item('banana')) # ['banana']
print(add_item('cherry')) # ['cherry']
Each call creates a fresh list, because the mutable default is now initialized inside the function body.
Rule of Thumb
Never use mutable objects as default arguments.
Stick to immutable defaults like:
None
0
''
(empty string)()
(empty tuple)
And initialize mutable structures — like lists or dicts — inside the function when needed.
Real-World Example: Caching Gone Wrong
Imagine a function intended to cache results:
def memoize(n, cache={}):
if n in cache:
return cache[n]
# Simulate computation
result = n * 2
cache[n] = result
return result
It works… but now cache
is shared across all calls! This can lead to incorrect data reuse and memory leaks if not handled carefully.
A better approach?
Use None
and initialize a local dictionary, or use decorators with care (like functools.lru_cache
for real memoization).
A Deeper Truth: It’s Not a Bug, It’s a Feature
Python isn’t broken — it’s just explicitly designed this way.
This behavior allows default values to be anything — including precomputed values, functions, even open file handles — without recreating them every call. It’s powerful, but it comes with sharp edges.
That’s why understanding this design choice is essential to mastering Python.
Final Thoughts
Mutable default arguments are a classic Python pitfall, and one that’s easy to fall into — especially when you’re learning the language or writing quick functions.
But once you get the hang of it, this “gotcha” becomes a tool in your toolbox. You’ll write safer, cleaner, and more predictable code — and be the person who explains this to the next confused developer 😄
Bonus Tip:
Use linters like flake8 or pylint — they often warn you when you’re using mutable default arguments!
Was this helpful? Follow me for more Python deep dives and coding insights you won’t find in the docs.