Question bank — Default parameters, keyword arguments
Warm-up pictures (build the mental model first)
Before the traps, three drawings you will keep pointing back to.
1 — Parameters vs arguments as a binding map. A parameter is an empty labelled box inside the function; an argument is a value that gets poured into that box when you call. Look at the arrows: the call values on the left flow into the parameter boxes on the right, and the box names are the labels you can address by keyword.

2 — Why "defaults drift right". The phrase defaults drift right just means: in a definition, every parameter WITHOUT a default must appear before any parameter WITH one. The figure shows why. Python fills boxes strictly left to right by position. If a default box sat on the left and a required box on the right, a single positional value would have no rule telling it which box to enter — the ✗ layout is ambiguous, so Python forbids it outright.

3 — The one-object-shared trap. For def add(item, bag=[]), the list [] is built ONCE, at the moment the def line runs — not on each call. The red list object is the same object every call reaches for, so appends pile up in it. The figure traces the arrows: three calls, one shared red list.

4 — Evaluation timing: def-time vs call-time. The timeline shows the default expression being evaluated at the ★ marked "def runs" — well before any call. Reassigning an outside variable after that ★ cannot change what was already frozen into the default.

5 — Defaults and DRY, concretely. Without a default, four call sites all repeat 5432, "utf8", 30; change the port once and you must hunt down every copy. Baking the value into the definition means one place to edit. The figure contrasts the two worlds — the red single source of truth vs the scattered duplicates.

See DRY principle - Don't Repeat Yourself for the general rule this illustrates.
True or false — justify
A default parameter is one that carries a fallback value like def f(x=10). True or false?
def, used only when the caller omits it. Everything below builds on this.The default value of a parameter is created fresh every time the function is called.
def line runs (see figure s03/s04). That single object is reused on every call, which is exactly what causes the mutable-default trap.In a definition, you may write def f(a=1, b).
a or b.box(height=5, width=2) and box(width=2, height=5) call the same function identically.
width=2, height=5.Once you use one keyword argument in a call, every argument after it must also be a keyword.
Using None as a default means the parameter can never hold a list.
None is only a sentinel saying "nothing was passed"; inside the function you replace it with a real list (if bag is None: bag = []), and callers may still pass their own list.A parameter and an argument are two words for the same thing.
def f(x) has parameter x; f(5) has argument 5.def connect(host, port=5432) forces every caller to type 5432.
5432 type nothing; only the unusual caller overrides it.Spot the error
Each line is broken or surprising. Name the exact fault.
def f(x=1, y): ... — what breaks and when?
SyntaxError at definition time: a non-default parameter y follows a default x (the ✗ layout in figure s02). Python rejects the def before it ever runs.greet("Asha", name="Asha") where def greet(name, greeting="Hello") — what happens?
TypeError: got multiple values for argument 'name' — the positional "Asha" already filled name, and the keyword tries to fill it again.f(a=1, 2) in a call — why is this illegal?
SyntaxError / positional-after-keyword: once a=1 is a keyword, the bare 2 has no slot Python can safely assign it to. Positionals must come first.def add(item, bag=[]): bag.append(item); return bag — what is the hidden bug?
[] is created once at def time (figure s03), so every call sharing the default accumulates into the same list: add(1)→[1], then add(2)→[1,2].def collect(x, seen=None): if seen == []: seen = []; ... — why is == [] the wrong test?
== [] also matches a real empty list the caller legitimately passed, so you'd silently throw away their object. Use is None, which asks "was nothing passed?".power(5, mod=7, 2) where def power(base, exp=2, mod=None) — what fails?
2 comes after the keyword mod=7, violating positional-before-keyword; it's a syntax error, not a logic bug.def f(a, b, /, c, *, d): ... then f(1, 2, 3, 4) — why does the call fail?
d is keyword-only (after the *), so it must be d=4; the bare 4 cannot fill it. TypeError: too many positional arguments.Why questions
Explain the reason, not just the rule.
Why must defaults come after non-defaults in a definition?
Why must positionals come before keywords in a call?
d=9, Python has stopped counting slots by position, so a later bare value has no place to land — the resolution rule would break.Why do keyword arguments matter when a function has many parameters?
box(width=2, height=5) reads clearly instead of a mystery like box(2,5,0,1).Why does the mutable-default bug exist at all — why not evaluate the default per call?
def executes to build the function object (figure s04); re-evaluating per call would be a different, more expensive design. Immutable defaults hide the sharing; mutable ones expose it. See Mutable vs immutable objects.Why is None the standard sentinel instead of, say, [] or 0?
None is a unique singleton that no meaningful data usually equals, so is None cleanly distinguishes "omitted" from "genuinely passed an empty/zero value".Why do defaults connect to the DRY principle?
Edge cases
Boundary and degenerate scenarios — cover every corner.
Can a caller pass an empty list to a bag=None function and have it used?
collect(1, seen=[]) passes a real empty list; since it's not None, the function appends into that list and returns it, respecting the caller's object.What if two calls to add(item, bag=None) each omit bag — are their lists independent?
bag is None and builds a fresh [], so the lists are separate. That is exactly the fix for the shared-mutable trap.Is a tuple default like def f(x=()) dangerous the way [] is?
Can every parameter have a default?
def f(a=1, b=2) is legal, and f() calls it with all defaults. Rule 1 is only violated when a non-default follows a default.What happens if you call power(5) where def power(base, exp=2, mod=None)?
exp=2, mod=None, giving 5**2 = 25 with no modulo — the tiny common-case call the defaults were designed for.Can you set a later parameter while keeping an earlier one at its default?
power(5, mod=7) skips exp (stays 2). Positionally it's impossible — you can't leave a hole — which is precisely why keyword arguments exist.Does giving a default change whether the parameter is local to the function?
What if a default references a variable defined outside — when is that value captured?
x=5; def f(a=x): ... freezes a's default to 5; later reassigning x does not change the default, because the expression was already evaluated.In def f(a, b, /):, what does the / do to defaults and keywords?
/ is positional-only: you may still give those parameters defaults, but callers can NEVER pass them by name. f(a=1) raises TypeError.In def f(*, key, mode="x"):, what does the leading * force?
* is keyword-only: key and mode can only be passed by name (f(key=1)), never positionally — even key, which has no default, must be named.Can a keyword-only parameter still carry a default?
def f(*, mode="x") gives mode a default AND makes it keyword-only. Omit it and the default applies; supply it only as mode=....Connections
- Functions - def and return
- Variable scope - local vs global
- *args and **kwargs (variadic functions)
- Mutable vs immutable objects
- DRY principle - Don't Repeat Yourself