Python Default Arguments

A default argument lets you give a function parameter a fallback value that is used automatically when the caller doesn’t supply one. Default arguments make functions more flexible and easier to call, since users only need to specify the parameters that matter to them while everything else falls back to a sensible value. They are one of the most commonly used features in Python function design, but they also hide a famous trap involving mutable objects that every Python developer eventually has to learn about.

Overview / How it works

When you define a function, you can assign a value to a parameter directly in the parameter list. If the caller omits that argument when calling the function, Python substitutes the default value instead of raising a TypeError for a missing argument.

The key thing to understand is when that default value is created. Default values are evaluated exactly once — at the moment the def statement runs (that is, when the function object is created), not each time the function is called. Python stores these evaluated defaults as a tuple on the function object itself, accessible via the __defaults__ attribute (and __kwdefaults__ for keyword-only defaults). Every call that omits the argument reuses that same pre-built object.

For immutable defaults like numbers, strings, booleans, or None, this is invisible and harmless — you can’t accidentally change an integer or a string in place. But for mutable defaults like lists, dictionaries, or sets, it means every call that relies on the default is sharing the exact same object in memory. If one call mutates that object, every future call sees the mutation. This is the single most common source of confusing bugs involving default arguments, and it’s covered in detail in the Common Mistakes section below.

Default arguments also interact with Python’s argument-binding rules: parameters without defaults are required, and once you give one parameter a default, every parameter after it (that isn’t keyword-only) must also have a default. This mirrors how positional arguments are matched left to right during a function call.

Syntax

def function_name(param1, param2=default_value, param3=default_value):
    # function body
    ...
Part Meaning
param1 A required (non-default) parameter — the caller must supply it.
param2=default_value An optional parameter. If the caller omits it, default_value is used.
Order rule All non-default parameters must come before any default parameters in the signature.
Calling Defaults can be overridden positionally (function_name(1, 2)) or by keyword (function_name(param1=1, param2=2)).

Examples

Example 1: A simple greeting function

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

print(greet("Alice"))
print(greet("Bob", "Hi"))
print(greet("Carol", greeting="Welcome"))
Hello, Alice!
Hi, Bob!
Welcome, Carol!

The greeting parameter has a default of "Hello", so greet("Alice") uses it automatically. The other two calls override the default, once positionally and once by keyword — both are valid ways to supply an argument that has a default.

Example 2: Multiple defaults in a realistic function

def create_order(item, quantity=1, price=9.99, express=False):
    total = quantity * price
    shipping = "Express" if express else "Standard"
    return f"{quantity}x {item} @ ${price:.2f} each = ${total:.2f} ({shipping} shipping)"

print(create_order("Notebook"))
print(create_order("Pen", quantity=5, price=1.50))
print(create_order("Laptop", quantity=2, price=899.99, express=True))
1x Notebook @ $9.99 each = $9.99 (Standard shipping)
5x Pen @ $1.50 each = $7.50 (Standard shipping)
2x Laptop @ $899.99 each = $1799.98 (Express shipping)

Here three parameters have defaults, letting callers specify only what differs from the common case. Notice how keyword arguments (quantity=5, express=True) make the call readable even though several parameters are being overridden out of order.

Example 3: Safely defaulting to a mutable value

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

cart1 = add_item("apple")
cart2 = add_item("banana")
print(cart1)
print(cart2)
['apple']
['banana']

Instead of writing cart=[] directly in the signature, this function defaults cart to None and builds a brand-new empty list inside the function body whenever no list is passed in. Each call that omits cart gets its own fresh list, so cart1 and cart2 stay independent. This None-sentinel pattern is the standard, idiomatic way to give a function a mutable-looking default.

How it works step by step / Under the hood

  • When Python executes a def statement, it builds a function object. As part of that, every expression used as a default value (e.g. 9.99, [], None) is evaluated right then, and the resulting objects are stored in the function’s __defaults__ tuple.
  • When you later call the function, Python does not re-run those default expressions. It simply checks which parameters were supplied by the caller (positionally or by keyword) and, for any that were not, pulls the matching value out of __defaults__.
  • Because the same object is pulled out every time, mutable defaults are literally shared state across all calls that rely on them — there is no hidden copying.
  • Argument binding happens left to right: positional arguments fill parameters in order first, then any remaining parameters are filled from keyword arguments or, failing that, from their defaults. If a required parameter ends up with neither a positional value, a keyword value, nor a default, Python raises TypeError: missing required positional argument.
  • You can inspect a function’s compiled-in defaults directly: for greet from Example 1, greet.__defaults__ would be the tuple ('Hello',).

Common Mistakes

Mistake 1: Using a mutable object as a default value

def add_item_bad(item, cart=[]):
    cart.append(item)
    return cart

print(add_item_bad("apple"))
print(add_item_bad("banana"))
['apple']
['apple', 'banana']

This looks like it should return a fresh one-item list each time, but it doesn’t. Because [] is evaluated once when add_item_bad is defined, every call that omits cart mutates and reuses that same list object. The second call sees the leftover "apple" from the first call — a bug that can silently corrupt data in real programs. The fix is the None-sentinel pattern from Example 3: default to None and create the mutable object fresh inside the function body.

Mistake 2: Putting a non-default parameter after a default one

Python requires all required (non-default) parameters to appear before any defaulted parameters in the signature. Writing something like:

def compute(a, b=10, c):
    return a + b + c

raises SyntaxError: non-default argument follows default argument before the function is even defined — Python rejects this at parse time. The fix is to reorder the parameters so required ones come first, e.g. def compute(a, c, b=10):, or give c a default as well.

Best Practices

  • Never use a mutable object (list, dict, set, or a custom mutable class instance) directly as a default value. Default to None and build the mutable object inside the function body instead.
  • Keep default expressions cheap and side-effect-free — they run once, at import/definition time, so anything expensive or order-dependent (like reading a config file) belongs inside the function body, not in the signature.
  • Order parameters as: required positional parameters, then defaulted parameters, then (optionally) *args and keyword-only parameters.
  • Prefer calling functions with keyword arguments when overriding a default that isn’t the very next parameter — it documents intent and avoids off-by-one mistakes.
  • Use defaults to express your function’s “normal” behavior; don’t hide required, always-needed values behind a default just to avoid typing them.
  • For functions with many optional parameters, consider a * in the signature to force some or all of them to be keyword-only, which prevents ambiguous positional calls as the function evolves.
  • Document non-obvious defaults in a docstring so callers don’t have to read the source to know what happens when they omit an argument.

Practice Exercises

  • Write a function power(base, exponent=2) that returns base raised to exponent, defaulting to squaring. Call it as power(5), power(2, 10), and power(exponent=3, base=4), and check the results.
  • Write a function append_to_log(entry, log=None) that safely appends entry to a list and returns it, using the None-sentinel pattern. Call it several times without passing log and confirm each call returns a list containing only its own entry.
  • Write a function describe_pet(name, species="dog", **traits) that returns a formatted string describing the pet using its name, species, and any extra keyword traits (like color="brown"). Call it with just a name, then with a custom species and two or three traits.

Summary

  • Default arguments let callers omit parameters, falling back to a value you specify in the function signature.
  • Default values are evaluated exactly once, when the function is defined, and reused on every call that omits that argument.
  • Because the same object is reused, mutable defaults (lists, dicts, sets) can leak state between unrelated calls — always default mutables to None and build them fresh inside the function.
  • All non-default parameters must precede defaulted parameters in a signature, or Python raises a SyntaxError.
  • Defaults can be overridden positionally or by keyword; keyword overrides are usually more readable when a function has several optional parameters.