Mutable defaults are shared state

The classic default-list bug and the sentinel pattern that prevents it

The classic Python gotcha: `def add_user(name, users=[])` creates the empty list once at function definition time, not on each call. That list is stored in `__defaults__` and reused every time the caller omits that argument. All subsequent calls share and mutate the same list object. The fix is the sentinel pattern: use `None` as the default, check with `is None`, and create a fresh container inside the function body. This is not a Python quirk. It follows directly from how the language evaluates default arguments at definition time. The same issue affects dataclasses via `field(default_factory=list)`, and dict patterns like `setdefault` where the default value is evaluated eagerly. <a href="/language-parameters">Learn how Python evaluates and stores default arguments</a>. <a href="/dict-setdefault">See the same eager-evaluation issue in dict patterns</a>.

Understand.
Visualize.
Master.

Python in Depth

An interactive engineering reference for Python internals

Quick note

Definition time is the trap.

:)
TABLE OF CONTENTS
2.3Mutable defaults are shared state

The classic default-list bug and the sentinel pattern that prevents it

You defined a function with bucket=[] as a default, called it twice, and watched accumulated values spill across calls. It looked like Python betrayed you. But the list was never meant to reset — it was created once when Python defined the function, stored on the function object, and reused on every call.

Think of it like an office coffee mug. One person fills it, drinks, and leaves it dirty. The next person finds yesterday's coffee and top it off. That is what mutable defaults do: they persist because the default expression runs only at definition time, not at call time.

Core answer

Do not use [], {}, or other mutable objects as defaults for per-call state. Use None plus an inside-the-function allocation, or use a unique sentinel when None is itself meaningful.

# [CURRENT - 3.10-3.14] Works on Python 3.x
class Bus:
def __init__(self, passengers=None):
self.passengers = list(passengers) if passengers is not None else []
def pick(self, name):
self.passengers.append(name)
Mechanism and internals

Default values are stored on the function object in __defaults__ and reused every time the caller omits that argument.

# [CURRENT - 3.10-3.14] Works on Python 3.x
def append_item(value, bucket=[]):
bucket.append(value)
return bucket
print(id(append_item.__defaults__[0]))
print(append_item("a"), id(append_item.__defaults__[0]))
print(append_item("b"), id(append_item.__defaults__[0]))

The object identity stays the same across calls. That is the whole bug.

This is language semantics, baked into the function definition contract. __defaults__ merely makes it observable.

Version context

The one-time default-evaluation rule is stable Python 3 behavior and documented in the function definition reference. Current project guidance targets Python 3.10-3.14. Python 3.9 and below are End-of-Life.

Edge cases and gotchas

None is only a good sentinel if None is not a meaningful caller value. Otherwise create a dedicated object and compare with is.

# [CURRENT - 3.10-3.14] Works on Python 3.x
_missing = object()
def configure(value=_missing):
if value is _missing:
return "use inherited default"
if value is None:
return "disable feature"
return f"set feature to {value!r}"

If your constructor receives caller-owned mutable data, snapshot it when shared mutation is not intended.

# [CURRENT - 3.10-3.14] Works on Python 3.x
class Team:
def __init__(self, members=None):
self.members = list(members) if members is not None else []
names = ["Ana"]
team = Team(names)
names.append("Bo")
print(team.members) # ['Ana']

Shared mutable defaults can be intentional for caches, but then they should be named, documented, bounded, and usually protected by synchronization in threaded code. Accidental shared state is the default bug.

Production usage

For dataclasses, use field(default_factory=...) instead of a mutable literal. That moves allocation to instance creation time, not class definition time.

# [OLDER / 3.9, CURRENT - 3.10-3.14] Works on Python 3.9+ [PEP 585]
from dataclasses import dataclass, field
@dataclass
class Batch:
rows: list[str] = field(default_factory=list)
Further depth
  • Language reference: function definitions
  • Python tutorial: default argument values
  • dataclasses.field
BOARD NOTESContext
WHY NO BENCHMARK?

This topic is better taught with structure, semantics, and cross-references than with a synthetic chart.

Definition time is the trap.

RELATED GUIDES
NEXT CHECKS
Contribute