PythonDefault & Keyword Arguments

Default & Keyword Arguments

Default arguments let you make a parameter optional by giving it a fallback value. Combined with keyword arguments, they make function calls more readable and functions more forgiving to use. They also hide one of Python's most notorious beginner traps, which this lesson covers in depth.

Setting Default Values

Assign a value to a parameter in the def line to give it a default. If the caller doesn't supply that argument, the default is used instead.

Python
def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")


greet("Ada")                 # uses default greeting
greet("Ada", "Welcome back") # overrides default
greet("Ada", greeting="Hi")  # override via keyword
Hello, Ada!
Welcome back, Ada!
Hi, Ada!

Parameters with defaults must come after parameters without defaults in the function signature — Python won't let you write def greet(greeting="Hello", name):.

Calling with Keyword Arguments for Clarity

When a function has several optional parameters, calling it with keywords makes the intent of each value obvious at the call site, instead of relying on position.

Python
def create_account(username, is_admin=False, is_active=True, max_logins=5):
    print(username, is_admin, is_active, max_logins)


# Hard to read — what do True and 10 mean here?
create_account("ada", True, True, 10)

# Much clearer:
create_account("ada", is_admin=True, max_logins=10)
ada True True 10
ada True True 10
Tip
Even when positional calls would work, prefer keyword arguments for anything boolean or numeric once a function has more than one or two optional parameters. It makes call sites self-documenting.
The Mutable Default Argument Bug

This is one of the most infamous gotchas in Python. It happens because default argument values are created once, when the function is defined — not each time the function is called.

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


print(add_item("apple"))            # ['apple']
print(add_item("banana"))           # ['apple', 'banana']  <- surprise!
print(add_item("cherry"))           # ['apple', 'banana', 'cherry']
['apple']
['apple', 'banana']
['apple', 'banana', 'cherry']
Warning
Every call that relies on the default reuses the *same* list object, because the empty list `[]` was created a single time when `def` ran. Mutating it in one call leaks into every future call. The same problem applies to any mutable default: lists, dicts, sets, or custom mutable objects.
The Fix: Use `None` as a Sentinel

The standard fix is to default to None and create a fresh mutable object inside the function body every time it runs.

Python
def add_item(item, basket=None):
    if basket is None:
        basket = []
    basket.append(item)
    return basket


print(add_item("apple"))    # ['apple']
print(add_item("banana"))   # ['banana']  <- fresh list each time
print(add_item("cherry"))   # ['cherry']
['apple']
['banana']
['cherry']

A common shorthand for this pattern is basket = basket or [], but be careful: this also replaces an empty list, empty string, or 0 passed in deliberately, since those are all falsy. The explicit if basket is None: check is safer and preferred in production code.

Defaults Are Evaluated Once, at Definition Time

The mutable default bug is really a symptom of a more general rule: default values are computed a single time, when Python reads the def statement — not on every call. This applies even to non-mutable expressions.

Python
import time


def log_event(message, timestamp=time.time()):
    print(f"[{timestamp}] {message}")


log_event("Server started")
time.sleep(1)
log_event("Still using the OLD timestamp")
[1751840000.1234] Server started
[1751840000.1234] Still using the OLD timestamp
Warning
Both calls print the *same* timestamp, because `time.time()` ran once when the function was defined, not each time it's called. If you need a fresh value per call (like the current time, or a new empty collection), compute it *inside* the function body instead of in the default value.
Quick Reference

Pattern

Safe?

Why

def f(x=[]):

Unsafe

Same list object reused across all calls

def f(x=None): x = x or []

Mostly safe

Fresh list each call, but treats falsy inputs as missing

def f(x=None): if x is None: x = []

Safe

Fresh list each call, only None triggers the default

def f(x=0):

Safe

Immutable defaults (numbers, strings, tuples, None) can't be mutated

Example
A function that appends a log entry to a dictionary of logs per user, using the `None` sentinel pattern to avoid sharing state between calls.

Python
def record_event(user, event, history=None):
    if history is None:
        history = {}
    history.setdefault(user, []).append(event)
    return history


h1 = record_event("ada", "login")
h2 = record_event("grace", "logout")
print(h1)
print(h2)
print(h1 is h2)  # False — independent dictionaries
{'ada': ['login']}
{'grace': ['logout']}
False
Note
Immutable defaults like numbers, strings, `True`/`False`, and `None` are always safe to use directly, because they can't be modified in place. The danger is specific to mutable defaults: `list`, `dict`, `set`, and custom mutable objects.