Defaults are evaluated once, at function definition time
The default expression is evaluated once, when the def statement executes, and the resulting object is stored on the function object (in defaults and kwdefaults). Every call that omits the argument receives that same object. For immutable defaults (None, numbers, strings, tuples) that is harmless. For a mutable default such as a list, dict or set, mutations persist between calls, producing state that leaks across callers and sometimes across requests or users.
The same root cause applies to non-obvious 'dynamic' defaults such as datetime.now(), time.time(), uuid4() or an object constructor: they are computed once at import time, so every call sees the same timestamp or ID. The fix is the None-sentinel pattern (or a private sentinel object when None is itself a valid argument), creating the value inside the function body. In dataclasses use field(default_factory=...); the dataclass machinery refuses mutable defaults, raising ValueError for list, dict or set in older versions, and since 3.11 for any unhashable default.
Trade-offs: the shared default can be used deliberately as a cheap cache or counter, but that is hidden global state and hard to test or reset; functools.lru_cache or an explicit object is clearer. The behavior is a design choice - evaluating at definition time makes defaults cheap and predictable - and late-bound default proposals (such as PEP 671) have been discussed, so check their status rather than assuming. For prevention, enable the flake8-bugbear rule B006 or the equivalent ruff rule so CI catches it. Common mistake: believing the bug only affects lists; any mutated default object, including dicts and custom class instances, behaves the same way.
0-2 years experience
2-5 years experience
5-8 years experience
8+ years experience