โ† Back

Home โ€บ Output practice โ€บ the mutable default argument

Predict the output: the mutable default argument

๐Ÿ”ฎ Predict the outputTopic: functionsDifficulty: hardNo login needed

The snippet

Two mental models collide here. Most learners assume basket=[] is built fresh every call. It is not โ€” Python evaluates default arguments once, when the def line runs, and every later call shares that one list.

def add(item, basket=[]):
    basket.append(item)
    return basket

print(add(1))
print(add(2))
print(add(3, []))
print(add(4))

Your prediction

Type exactly what this program prints โ€” one line per print() call:

The output

[1]
[1, 2]
[3]
[1, 2, 4]

Verified against CPython 3.11 in this repo's build

Line-by-line explanation

  1. def add(item, basket=[]):
    The empty list is created now and stored inside the function object as add.__defaults__. Every call that omits the second argument receives *that same list*.
  2. print(add(1))
    basket starts as the shared [], 1 is appended, and the function returns it โ†’ [1].
  3. print(add(2))
    The shared list already holds 1. Appending 2 gives [1, 2] โ€” the previous call's item is still there, which is exactly what makes this a bug in real code.
  4. print(add(3, []))
    A list is passed explicitly, so the default is bypassed entirely. This call uses a brand-new list โ†’ [3], and the shared default is untouched (still [1, 2]).
  5. print(add(4))
    Back to the shared default: [1, 2] + 4 โ†’ [1, 2, 4]. The [3] from the third call never joined it.

The rule you can reuse

Default arguments are evaluated once at definition time. Use basket=None and then if basket is None: basket = [] inside the body when you want a fresh list per call.

Why most predictions break here

Answering [1] [2] [3] [4]. That is what happens if a new list really were created per call โ€” the third line (which does get a fresh list) is the control experiment that proves it does not.

Quick fire

Same rule, two more shapes. Predict first, then open.

Predict this too
def f(x=[]):
    x.append(1)
    return len(x)

print(f(), f(), f())

Output
1 2 3

The same list is counted three times, so the length grows: 1 2 3.

Predict this too
def f(x=None):
    if x is None:
        x = []
    x.append(1)
    return len(x)

print(f(), f(), f())

Output
1 1 1

The None sentinel builds a new list on every call, so the length never accumulates: 1 1 1.

Share your result

Challenge a friend โ€” the link contains the question, never the answer:

Copied โœ“

Keep practising this rule

More output questions โ†’ All interactive problems

Questions this page answers

Is this only about lists?

No โ€” any mutable default is shared: dict, set, custom class instances, or a list inside a tuple. An int, str or tuple default looks safe only because you cannot mutate it in place, so rebinding it hides the sharing.

Does the interview question change if I pass an argument?

Yes, and that is the trick in line 3: passing [] explicitly skips the default, so that call starts clean while the others keep accumulating.