🐍 Python: The Mutable Default Argument Trap

You write a function that collects items into a list. The first call works perfectly. The second call mysteriously contains items from the first.

No global variable. No database. No spooky background thread.

Just a Python default argument doing exactly what it was designed to do.

The discovery: Defaults are evaluated once

Consider this innocent-looking function:

def add_item(item, items=[]):
    items.append(item)
    return items

print(add_item("coffee"))  # ['coffee']
print(add_item("cake"))    # ['coffee', 'cake']

You might expect each call to start with an empty list. It doesn’t.

Python evaluates default argument expressions when the function is defined, not each time it’s called. Both calls reuse the same list object because neither supplies an explicit items argument.

The list isn’t recreated between calls. It’s quietly keeping receipts.

See the official Python tutorial on default argument values.

The fix: Use None as the default

For a function that needs a fresh list on each call, use a sentinel such as None:

def add_item(item, items=None):
    if items is None:
        items = []

    items.append(item)
    return items

print(add_item("coffee"))  # ['coffee']
print(add_item("cake"))    # ['cake']

Now the function creates a new list whenever the caller omits items. If the caller explicitly provides a list, that list is still modified.

That distinction is important: the function doesn’t promise never to mutate a supplied list. It only avoids accidentally sharing its default list between calls.

The important nuance: Mutable defaults aren’t always wrong

A default argument that intentionally retains state can be useful in specialized designs. The problem is accidental shared state, especially with lists, dictionaries, and sets.

For dataclasses, there’s a related pattern worth knowing. Use default_factory when each instance needs its own mutable default:

from dataclasses import dataclass, field

@dataclass
class ShoppingList:
    items: list[str] = field(default_factory=list)

Each ShoppingList instance receives a list created by the factory rather than sharing one list across instances.

See the official dataclasses documentation.

The takeaway β˜•

If a function needs a fresh mutable value per call, don’t put that value directly in the default argument.

Use None and create the object inside the function. For dataclasses, reach for default_factory.

One tiny =[] can save a line of codeβ€”and cost an afternoon of debugging.

Related articles