← Back to Python

Mutable Default Arguments

PythonEntrypython

The Question

Why are mutable default arguments dangerous in Python?

What a Strong Answer Covers

  • created once at definition
  • "shared across calls
  • "None default fix

Senior-Level Answer

In Python, default argument values are evaluated **once** when the `def` statement is executed — not each time the function is called. For immutable defaults like integers, strings, or `None`, this is harmless because they cannot be modified in place. For mutable defaults like lists, dicts, or sets, this becomes a subtle and persistent bug.

The canonical example:

```python def append_to(element, to=[]): to.append(element) return to

append_to(1) # [1] append_to(2) # [1, 2] — not [2]! append_to(3) # [1, 2, 3] ```

All three calls share the same list object. The default `[]` is stored in `append_to.__defaults__[0]` and lives for the lifetime of the function object. Each mutation persists across calls because no new list is created.

Why does Python work this way? Default values are computed at definition time for performance and to allow complex objects (like compiled regexes) to be cached as defaults. It is a deliberate design decision, not an oversight — but it consistently surprises developers.

The idiomatic fix is the **sentinel pattern**: use `None` as the default and create the mutable object inside the function body:

```python def append_to(element, to=None): if to is None: to = [] to.append(element) return to ```

Now each call that omits `to` gets a fresh list. Callers who explicitly pass a list get mutation on that list, which is expected.

The same bug applies to dicts and custom objects:

```python def add_user(name, cache={}): cache[name] = True return cache ```

Python dataclasses handle this correctly by requiring `field(default_factory=list)` for mutable defaults and raising a `ValueError` if you try to assign a mutable default directly. This is an intentional guardrail.

The pattern is also intentionally exploited in rare cases: a mutable default as a function-level cache (poor man's `functools.lru_cache`). But this is an advanced idiom, not a recommendation for general use.

In code review, any mutable default argument should be flagged. Linters like `flake8` (B006 via `flake8-bugbear`) catch this automatically.

What Separates a 2/3 from a 3/3

2/3 — Passing but Incomplete

Correctly explains that defaults are evaluated once at definition time, shows the list mutation bug, and gives the None sentinel fix.

3/3 — Strong Answer

Covers definition-time evaluation, explains __defaults__ storage, shows the fix, mentions dataclass's field(default_factory=...) guardrail, and knows the deliberate cache exploitation pattern.

Common Mistakes

  • Saying Python 'reuses the default value' without explaining it's the same object — the issue is mutation of a shared object
  • Not knowing that immutable defaults are safe — the problem is specific to mutability
  • Forgetting that the fix is per-call allocation via None sentinel, not 'just avoid lists as defaults'
  • Not mentioning flake8-bugbear B006 or dataclass enforcement as real-world safeguards

Follow-Up Questions

  • Where is the default value stored on a function object? — In function.__defaults__ (positional) and function.__kwdefaults__ (keyword-only). Inspect it to see the mutation in action.
  • How do Python dataclasses prevent this mistake? — Assigning a list or dict directly as a field default raises ValueError. You must use field(default_factory=list).
  • Is there ever a case where a mutable default is intentional? — Yes — as a function-level persistent cache. Rare and should be heavily documented. functools.lru_cache is the correct tool.
  • Does this affect class-level mutable attributes in the same way? — Yes — class variables that are mutable are shared across instances unless overwritten per-instance in __init__.

Related Questions

  • GIL — What It Is and What It Protects
  • ThreadPoolExecutor & ProcessPoolExecutor
  • Decorators — Under the Hood
  • Generators & yield
  • Context Managers

Can You Explain This Cold?

Reading the answer is step one. Explaining it unprompted — under interview pressure — is what actually matters. Get AI-graded feedback on your answer with follow-up probes on your weak points.

Get Graded — Free Assessment
GrindQuestionsAITechnical interview assessment
TermsPrivacyAbout