promptdojo_

Arguments, defaults, and the silent wrong-order bug — step 3 of 9

Default values — how AI builds "knobs" into a function

Here's a thing that's about to happen all year: you'll ask Cursor for a function that creates a record, sends a notification, or runs a job. It comes back with twelve parameters. Most of them have an = sign and a value already filled in. Eleven you'll never touch. One matters this week.

That's a function with default arguments. The defaults are the "sensible behavior unless you say otherwise" knobs. AI uses them constantly — sometimes for good (sane defaults you can override) and sometimes badly (mutable defaults, which we'll get to).

The mental model

A parameter with a default has two states: the caller either passes a value, or they don't. If they don't, Python uses the default.

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

name is required — no default, you must pass it. greeting is optional — pass it if you want, otherwise Python uses "hi".

The editor on the right calls greet three ways:

greet("alex")                    # → "hi, alex"
greet("alex", "yo")              # → "yo, alex"
greet("alex", greeting="howdy")  # → "howdy, alex"

Same function. Three different outputs. The defaults are what parameters fall back to when the caller stays quiet.

Why this is everywhere in AI code

Cursor writes default-heavy signatures because they're how you make the same function work for the 80% case and the 20% case without forking it. Every API client, request builder, retry helper, formatter — they all show up like:

def fetch(url, *, timeout=30, retries=3, headers=None, verify=True):
    ...

When you're reading that, the defaults also tell you what the function's normal behavior is. retries=3 says "by default, this retries three times." That's a piece of behavior documented right in the signature.

The rule that bites everyone

Defaulted parameters must come after non-defaulted ones. This is illegal:

def greet(greeting="hi", name):   # SyntaxError
    ...

Python needs the optional ones at the end so it knows when positional arguments stop being required. If you ever try to add a default in the middle of an existing parameter list, Python will refuse — you have to move it to the end or default everything to its right too.

Where AI specifically gets defaults wrong

The single most famous Python footgun, and Cursor still falls into it: mutable default arguments.

def add_tag(item, tags=[]):
    tags.append(item)
    return tags

This looks fine. It is not. The list [] is created once, when the function is defined, and then the same list is reused for every call that doesn't pass tags. Calls accumulate state across each other:

add_tag("first")    # → ["first"]
add_tag("second")   # → ["first", "second"]   <- where did "first" come from?!

The fix is the convention you'll see in good Python code: use None as the default and create the real default inside the function.

def add_tag(item, tags=None):
    if tags is None:
        tags = []
    tags.append(item)
    return tags

Mutable defaults — lists, dicts, sets — should always be None in the signature. When you see tags=[] or headers={} in AI-generated code, flag it. That's a bug pattern Cursor still ships in 2026, and you should be the one who catches it.

Run the editor. Three calls, three outputs, one definition.

read, then continue.