The default value [] is created once, when the function is defined, not each time it is called. So every call that uses the default shares the same list, and items from earlier calls stay in it.
Seeing the bug
def add(item, items=[]):
items.append(item)
return items
add("a") # ['a']
add("b") # ['a', 'b'] <- the list from the first call is still there
add("c") # ['a', 'b', 'c']Each call looks independent, but they all use the same list object that Python created when it read the def line. You can see it: add.__defaults__ shows the list growing.
The fix: use None as a marker
def add(item, items=None):
if items is None:
items = []
items.append(item)
return itemsA new list is created inside the function on each call. Use is None and not if not items, because an empty list passed by the caller is falsy, and you would replace it by accident.
The same bug with other mutable types
Dictionaries, sets and any mutable object used as a default behave the same way. In pipeline code it often appears like this:
def run_job(name, config={}): # one shared dict for every job
config["name"] = name # leaks into the next callTwo jobs run in the same process would share and overwrite each other's settings, a hard bug to find because it depends on call order.
Where defaults are fine
Immutable defaults such as numbers, strings, tuples, None and True cannot be changed in place, so sharing them is harmless.
A deliberate use
Some people use a mutable default as a cheap cache on purpose. It works, but it is obscure. Use functools.lru_cache or an explicit module-level dictionary so the intent is clear.
What interviewers want
They ask it to see whether you know that defaults are evaluated at definition time, and that you can name the None pattern. Linters (flake8-bugbear, ruff) flag it automatically, so mention that you would let tooling catch it.