Skip to main content
Articles/python-pitfalls/python-s-tricky-default-arguments
Scenario Article

Python's Tricky Default Arguments

Ever wonder why your Python function's default list keeps growing unexpectedly? It's a common pitfall involving mutable defaults!

Abstract Algorithmsβ€’Jul 13, 2026
XLinkedInReddit
Python's Tricky Default Arguments
πŸ’‘ Executive Summary

Python's default arguments are evaluated once when the function is defined, not on each call.

🎯 System Scenario & Requirements

System Goal

Python's default arguments are evaluated once when the function is defined, not on each call.

Actors & Components

  • Application or API tier
  • Core service under design
  • Operator or SRE observer

Functional Requirements

  • Python's default arguments are evaluated once when the function is defined, not on each call.
  • If a default argument is a mutable object (like a list or dictionary πŸ“), all subsequent calls that don't override that argument will share the same mutable object, leading to surprising side effects and difficult-to-debug bugs πŸ›.
  • Have you ever encountered a Python function where a default list or dictionary parameter seemed to retain changes from previous calls, even though you expected it to be fresh each time?

Constraints & Assumptions

  • Operational latency, consistency levels, and network partition boundaries must be enforced.

⚑ Execution Stages & Decision Trace

1

Stage 1 β€” The Unexpected Behavior

Action / Decision:

Consider a function designed to add an item to a list, with an empty list as its default: What would you expect the output of the last line to be?

Component Behavior:

Many new Pythonistas expect [4] , but the actual output reveals the shared state:

Observable Output: Many new Pythonistas expect [4] , but the actual output reveals the shared state:
Read Full Stage in Deep Dive ↓
πŸ“–

Deep Dive

Detailed Architectural Analysis

Deep Dive: Stage 1 β€” The Unexpected Behavior

Consider a function designed to add an item to a list, with an empty list as its default:

def add_to_list(item, my_list=[]):
    my_list.append(item)
    return my_list

print(add_to_list(1))
print(add_to_list(2))
print(add_to_list(3, ['a', 'b']))
print(add_to_list(4))

What would you expect the output of the last line to be? Many new Pythonistas expect [4], but the actual output reveals the shared state:

[1]
[1, 2]
['a', 'b', 3]
[1, 2, 4] # Wait, what?!

Observable outcome: Many new Pythonistas expect [4] , but the actual output reveals the shared state:

Deep Dive: Stage 2 β€” The Fix: Using None

The standard and recommended solution is to use None as the default value, and then check inside the function body to initialize a new mutable object if None was passed. This ensures a fresh mutable object is created for each call that relies on the default.

def add_to_list_fixed(item, my_list=None):
    if my_list is None:
        my_list = [] # A new list is created only if no list was provided
    my_list.append(item)
    return my_list

print(add_to_list_fixed(1))
print(add_to_list_fixed(2))
print(add_to_list_fixed(3, ['a', 'b']))
print(add_to_list_fixed(4))

Now, the output is as expected:

[1]
[2]
['a', 'b', 3]
[4]

Observable outcome: Now, the output is as expected:

πŸ“Š System Outcome & Trade-offs

End-to-End Result: Always be wary of using mutable objects (lists, dictionaries, sets, custom class instances) as default arguments in Python.

Observed User & Operator Behavior

User: The None idiom is your reliable pattern for ensuring that default mutable arguments behave as independent instances for each function call, preventing unexpected shared state and making your code more predictable and robust.

Operator: Always be wary of using mutable objects (lists, dictionaries, sets, custom class instances) as default arguments in Python.

Key Architectural Trade-offs

  • System guarantees are traded against replication overhead and failover convergence times.
βœ…

Key learnings

  • Always be wary of using mutable objects (lists, dictionaries, sets, custom class instances) as default arguments in Python.
  • The None idiom is your reliable pattern for ensuring that default mutable arguments behave as independent instances for each function call, preventing unexpected shared state and making your code more predictable and robust.
  • Consider a function designed to add an item to a list, with an empty list as its default: What would you expect the output of the last line to be?
  • The standard and recommended solution is to use None as the default value, and then check inside the function body to initialize a new mutable object if None was passed.
NEXT LEARNING STEPApply & Defend Decisions

Apply Concepts from this Scenario

Test your understanding of the architecture decisions and trade-offs presented in this case study.

Share this case study

Discuss architecture trade-offs and lessons with your team.

Share:XLinkedInReddit