This post covers what is arguably one of the most famous and confusing “gotchas” in the Python language. In Post #89, we learned how to use default parameter values, which is a fantastic feature for making functions more flexible. However, using a mutable type (like a list or a dictionary) as a default can lead to completely unexpected and buggy behavior.
We will demonstrate this pitfall, explain exactly why it happens, and show you the correct, professional pattern to avoid it.
The Unexpected Behavior
Let’s write a seemingly innocent function. It’s supposed to add an item to a list. If we don’t provide a list, it should create a new list for us, add the item, and return it.
def add_to_list(item, my_list=[]):
my_list.append(item)
return my_list
Now, let’s call it a few times without providing the my_list
argument. We would expect each call to return a new list containing just one item.
# First call
list1 = add_to_list(1)
print(f"List 1: {list1}")
# Second call
list2 = add_to_list(2)
print(f"List 2: {list2}")
# Third call
list3 = add_to_list(3)
print(f"List 3: {list3}")
The output is baffling and wrong:
List 1: [1]
List 2: [1, 2]
List 3: [1, 2, 3]
Instead of creating a new list each time, the function seems to be remembering the items from the previous calls!
Why Does This Happen? The Single Object Rule
This strange behavior happens because of a core rule in Python:
Default parameter values are created only ONCE, at the moment the function is defined, not each time the function is called.
When Python first reads def add_to_list(item, my_list=[]):
, it creates a single, empty list object in memory. The my_list
parameter gets a reference to this one specific list as its default.
Every subsequent call to Notes
that doesn’t provide its own list argument will use and modify that exact same list object. The first call appends 1
to the shared list. The second call appends 2
to that same list, which already contains 1
.
The Correct Pattern: Use None
as the Default
The standard and “Pythonic” way to safely have a mutable default is to use None
as the default value, and then create a new list inside the function if one isn’t provided. None
is immutable, so it is completely safe to use as a default.
The pattern is:
- Set the default value for the parameter to
None
. - Inside the function, check if the parameter is
None
. - If it is, create a new, empty mutable object (like a list).
Here is the corrected version of our function:
def add_to_list_safe(item, my_list=None):
if my_list is None:
my_list = [] # Create a new list only for this function call
my_list.append(item)
return my_list
This pattern ensures that a fresh, new list is created for every single function call that relies on the default.
The Correct Behavior in Action
Now, let’s run our test again with the safe version of the function.
# First call
list1 = add_to_list_safe(1)
print(f"List 1 (safe): {list1}")
# Second call
list2 = add_to_list_safe(2)
print(f"List 2 (safe): {list2}")
# Third call
list3 = add_to_list_safe(3)
print(f"List 3 (safe): {list3}")
The output is now exactly what we originally expected:
List 1 (safe): [1]
List 2 (safe): [2]
List 3 (safe): [3]
What’s Next?
The mutable default argument pitfall is a classic Python rite of passage. Remember the rule: Never use a mutable type (like a list or dictionary) as a default parameter value. Instead, use None
as a default and create the mutable object inside the function. This will save you from some very confusing bugs.
Our functions have been doing work and printing things, but they haven’t been giving data back to the part of the code that called them in a structured way. To do that, we need a special keyword. In Post #91, we will explore the return
statement.
Author

Experienced Cloud & DevOps Engineer with hands-on experience in AWS, GCP, Terraform, Ansible, ELK, Docker, Git, GitLab, Python, PowerShell, Shell, and theoretical knowledge on Azure, Kubernetes & Jenkins. In my free time, I write blogs on ckdbtech.com