Mutable Default Argument

A default argument value is created once, when the function is defined — using a mutable object as one silently shares state across every call that relies on it.

Purpose

Python evaluates a parameter’s default value exactly once — at the moment def runs, not on every call. For an immutable default (0, "", None) this is invisible. For a mutable one ([], {}, set()) it means every call that doesn’t pass its own value shares and mutates the same object.

Example

# Broken: the default list is created once and shared by every call.
def add_item_broken(item, basket=[]):
    basket.append(item)
    return basket


add_item_broken("apple")
add_item_broken("banana")  # "apple" is still in there
add_item_broken("apple")   ->  ['apple']
add_item_broken("banana")  ->  ['apple', 'banana']   # surprise
# Fixed: use None as the default, and create a fresh list inside the function.
def add_item(item, basket=None):
    if basket is None:
        basket = []
    basket.append(item)
    return basket

Behavior notes

  • The trap applies to any mutable default: [], {}, set(), or an instance of a mutable custom class. It does not apply to immutable defaults (None, 0, "", a tuple) — there’s nothing to mutate, so nothing to share unexpectedly.
  • The fix shown above (basket=None, then if basket is None: basket = []) is the standard idiom — it defers creating the mutable object to call time, inside the function body, where a fresh one is made on every call that needs one.
  • Occasionally the sharing is intentional — a cache or memo dict that’s meant to persist across calls is a legitimate, if uncommon, use of this exact behavior. That’s rare enough that it’s worth a comment explaining the intent when it’s genuinely what’s wanted.

Common mistakes

  • Writing def f(items=[]): out of habit, without realizing the empty list is created once, not per call — this is by far the most common way beginners (and not only beginners) hit this trap.
  • “Fixing” it by clearing the list inside the function (basket.clear()) instead of creating a new one — this still shares one object across every caller that holds a reference to a previous call’s result, which usually isn’t what’s wanted either.

Implementation notes

Default-argument evaluation timing is defined by the language itself (see the official FAQ entry linked above) — identical, and identically surprising the first time, across every conforming Python implementation.

Taught in these lessons