DEV Community

Emily Scott
Emily Scott

Posted on

Why Your Python Default Arguments Keep Changing (And How to Fix It)

A subtle and dangerous Python bug many developers encounter is this:

Your function works once, then starts behaving strangely on later calls.

Data keeps growing.

Values persist unexpectedly.

And you never intended that.

This happens a lot in:

  • API handlers
  • Utility functions
  • Data processing scripts
  • Django / Flask apps
  • Caching logic
  • Logging helpers

It feels random, but it is not.

Let’s fix it step by step.


The Problem

Suppose you write this:

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

print(add_item("apple"))
print(add_item("banana"))
Enter fullscreen mode Exit fullscreen mode

Expected Output

["apple"]
["banana"]
Enter fullscreen mode Exit fullscreen mode

Actual Output

["apple"]
["apple", "banana"]
Enter fullscreen mode Exit fullscreen mode

Very confusing.


Why This Happens

In Python, default arguments are evaluated only once.

This line:

items=[]
Enter fullscreen mode Exit fullscreen mode

runs only when the function is defined—not each time it is called.

So the same list is reused across all calls.


What’s Really Happening

This code:

def add_item(item, items=[]):
Enter fullscreen mode Exit fullscreen mode

behaves like:

shared_list = []

def add_item(item, items=shared_list):
Enter fullscreen mode Exit fullscreen mode

Every call uses the same list.

That is why values keep accumulating.


Step 1: Fix Using None

Correct solution:

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

    items.append(item)
    return items
Enter fullscreen mode Exit fullscreen mode

Output Now

["apple"]
["banana"]
Enter fullscreen mode Exit fullscreen mode

Now each call gets a fresh list.


Why This Fix Works

Because None is safe and immutable.

Each function call creates a new list only when needed.


Real Example

Bad:

def create_user(name, roles=[]):
    roles.append("user")
    return {"name": name, "roles": roles}
Enter fullscreen mode Exit fullscreen mode

Correct:

def create_user(name, roles=None):
    if roles is None:
        roles = []

    roles.append("user")
    return {"name": name, "roles": roles}
Enter fullscreen mode Exit fullscreen mode

Another Case

Bad:

def update_config(config={}):
    config["debug"] = True
    return config
Enter fullscreen mode Exit fullscreen mode

Correct:

def update_config(config=None):
    if config is None:
        config = {}

    config["debug"] = True
    return config
Enter fullscreen mode Exit fullscreen mode

Quick Debug Rule

If data keeps "remembering" old values:

Ask yourself:

Am I using a mutable default argument?


Final Thoughts

This is not a bug—it is Python behavior.

Remember:

  • Default arguments run once
  • Lists and dicts are mutable
  • Mutable defaults create shared state
  • Always use None as default

This small fix prevents big bugs.


Your Turn

Have you seen this bug before?

Most Python developers remember the first time it happened.

Peace,
Emily Idioms

Top comments (0)