This tiny function looks completely fine. It has a bug that has fooled beginners and senior developers alike:
def add_item(item, items=[]): items.append(item) return items print(add_item("apple")) # ['apple'] print(add_item("banana")) # ['apple', 'banana'] 😱 The second call returns ['apple', 'banana']. Where did apple come from? We never passed a list in.
Here's the 2-minute video version if you prefer watching:
Why it happens: defaults are created once
Default values are evaluated once, when the function is defined, not every time it's called. Every call that doesn't pass items shares the same list object.
You can actually see it, hiding inside the function:
>>> add_item("apple") ['apple'] >>> add_item("banana") ['apple', 'banana'] >>> add_item.__defaults__ (['apple', 'banana'],) The default list lives on the function object and grows with every call.
The fix: use None and create the list inside
def add_item(item, items=None): if items is None: items = [] items.append(item) return items Now every call that doesn't pass a list gets its own brand-new one. This is the standard Python pattern, and you'll see it all over the standard library.
Same trap, different shape: frozen timestamps
It's not just lists. Anything in a default is evaluated once:
from datetime import datetime def log(msg, when=datetime.now()): # evaluated once! print(when, msg) log("first") log("second") # same time as "first" 🤔 datetime.now() runs only when the function is defined, so every log line gets the same frozen timestamp, even hours later. Same fix: default to None, and compute the time inside the function.
Dataclasses protect you (use default_factory)
Dataclasses refuse mutable defaults outright:
@dataclass class Cart: items: list = [] # ValueError: mutable default <class 'list'> for field items is not allowed: use default_factory The correct way:
from dataclasses import dataclass, field @dataclass class Cart: items: list = field(default_factory=list) default_factory builds a new list for every instance.
Bonus pitfall: items = items or []
You'll often see this shortcut:
def fill(items=None): items = items or [] # [] is falsy! items.append(1) mine = [] fill(mine) print(mine) # [] (your list was ignored!) An empty list is falsy, so if the caller passes their own empty list, it gets silently replaced with a new one. Always check is None explicitly:
def fill(items=None): if items is None: items = [] items.append(1) mine = [] fill(mine) print(mine) # [1] ✅ Recap
- Default values are evaluated once, at definition time
- Never use
[],{}orset()as defaults - Use
Noneand create the object inside the function - In dataclasses:
field(default_factory=list) - Check
is None, notor
Have you been bitten by this one? Tell me in the comments.
I post one tested coding tip every day on YouTube at Refactorly. The 30-second version of this one is here.

