The Secret Life of Python: The Default Trap

Published: (February 27, 2026 at 12:37 AM EST)
2 min read
Source: Dev.to

Source: Dev.to

Why you should never use empty lists as default arguments.

🎧 Audio Edition: Prefer to listen? Check out the expanded AI podcast version of this deep dive on YouTube.

📺 Video Edition: Prefer to watch? Check out the 7‑minute visual explainer on YouTube.

The Shared Memory

def add_student(name, student_list=[]):
    student_list.append(name)
    return student_list

# First call
print(add_student("Alex"))

# Second call
print(add_student("Alice"))

The second call returns ['Alex', 'Alice'] because the default list is created once when the function is defined, not each time it is called.

Seeing the “Zombie” Variable

You can inspect the stored default values via the __defaults__ attribute:

print(f"Before: {add_student.__defaults__}")
add_student("Alex")
print(f"After:  {add_student.__defaults__}")

Output

Before: ([],)
After:  (['Alex'],)

The list is attached to the function object and persists across calls. This behavior only occurs with mutable defaults (e.g., lists, dictionaries). Immutable defaults such as None, strings, or integers are safe because they cannot be altered.

The Professional Standard

The recommended pattern is to use None as a sentinel and create a new list inside the function:

def add_student(name, student_list=None):
    if student_list is None:
        student_list = []
    student_list.append(name)
    return student_list

Now a fresh list is created on each call when no explicit list is provided.

Margaret’s Cheat Sheet: Default Arguments

  • Safe defaults: None, True, False, 0, "" (immutable objects)
  • Dangerous defaults: [], {}, set() (mutable objects)

The Problem: Default arguments are evaluated once at definition time.

The Solution: Use None as the default and initialize the mutable object inside the function body.

The Debugger: Inspect my_function.__defaults__ to detect hidden shared state.

0 views
Back to Blog

Related posts

Read more »