Code Review · Python · Correctness

Python Code Review: Mutable Default Argument

Review a Python function that accidentally shares a default list across calls.

Code ReviewPythonFunctionsBugs

Code Review Exercise

An interviewer shows you this Python function and asks you to review it. What issues do you see?

python
def add_user(user, users=[]):
    users.append(user)
    return users


print(add_user("Alice"))
print(add_user("Bob"))
Before reading the answer, pause and ask: what should the second print statement output?

What Actually Happens

Many people expect each call to start with a new empty list. But the default list is created once when the function is defined, not each time the function is called.

text
['Alice']
['Alice', 'Bob']

The second call reuses the same list from the first call. That means state leaks between function calls.

Review Findings

Issue #1: Mutable default argument

The default value users=[] is a list. Lists are mutable, and Python creates default argument objects only once when the function is defined.

Issue #1: Mutable default argument

Each call without the users argument shares the same list. This makes the function behavior depend on previous calls, which is surprising and bug-prone.77

Issue #1: Mutable default argument

The function both appends to a list and returns the list. In a real code review, you may ask whether this function should mutate an existing list or create a new one.

Why This Happens

Python evaluates default argument values at function definition time. For immutable values like strings, numbers, or None, this usually does not surprise people. For mutable values like lists, dictionaries, or sets, it can cause unexpected shared state.

Safe default values: None, strings, numbers, booleans.
Dangerous default values: lists, dictionaries, sets, or custom mutable objects.

Fixed Version

Use None as the default value, then create a new list inside the function.

python
def add_user(user, users=None):
    if users is None:
        users = []

    users.append(user)
    return users


print(add_user("Alice"))
print(add_user("Bob"))
text
['Alice']
['Bob']

Even Clearer Version

If the goal is to create a new list every time, avoid the optional list parameter entirely.

python
def create_user_list(user):
    return [user]

If the goal is to mutate an existing list, make that explicit.

python
def add_user_to_list(user, users):
    users.append(user)
    return users

How To Explain This In An Interview

A strong review answer does not just say, “mutable default argument.” It explains the impact.

I would avoid using a list as a default argument here. Python creates that list once when the function is defined, so calls that omit the second argument will share the same list. That can leak state between calls. I would default to None and create a new list inside the function.

Common Interview Follow-Ups

Are all default arguments bad in Python?

No. Immutable defaults like None, strings, numbers, and booleans are usually fine. The problem is mutable defaults like lists, dictionaries, and sets.

Why use None instead of an empty list?

None is immutable and works as a sentinel value. It lets the function detect that no list was provided and create a fresh list for that call.

Is this ever intentionally used?

Sometimes advanced Python code uses mutable defaults for caching, but that is unusual and should be very explicit. In interview and production code, avoid it unless there is a clear reason.

What other mutable defaults should you watch for?

Dictionaries, sets, lists, bytearrays, and custom mutable objects can all cause the same shared-state problem.

Final Takeaway

In Python code reviews, always check function defaults. If you see [], , or set() as a default argument, stop and ask whether state might leak between calls.