Parameters, Arguments, Defaults, and Keywords

Key Takeaways

  • Parameters are the names listed in a function definition; arguments are the actual values supplied during a call.
  • Positional arguments bind left to right; keyword arguments name a specific parameter regardless of order.
  • A required positional parameter cannot be omitted, and parameters with defaults must follow all required parameters.
  • Default expressions are evaluated once when the def statement runs, not on every call, which is the mutable-default trap.
  • Missing, duplicate, unexpected, or mis-ordered arguments all raise TypeError before the body runs.
Last updated: June 2026

Parameters versus arguments

A parameter is a name written in the function header. An argument is a value the caller supplies. PCEP deliberately uses meaningless names like a, b, and c so you cannot guess from semantics; you must bind the call mechanically.

def power(base, exponent):
    return base ** exponent

print(power(2, 3))

Here base and exponent are parameters; 2 and 3 are arguments. The call is positional, so base gets 2, exponent gets 3, and the result is 8.

Positional and keyword binding

Arguments pass by position (matched left to right) or by keyword (the caller names the target parameter). Keyword arguments can reorder freely.

def describe(name, score, passed=False):
    return name + ':' + str(score) + ':' + str(passed)

print(describe('Mia', 82))
print(describe(score=91, name='Sol', passed=True))

Both calls are valid. The first uses the default passed=False; the second rewrites the order but the keyword names keep the binding unambiguous, producing Sol:91:True.

Which call patterns are legal

Call patternValid?Reason
f(1, 2)YesPositional values bind left to right
f(a=1, b=2)YesKeywords name the parameters
f(a=1, 2)NoA positional argument may not follow a keyword argument
f(1, a=2)Usually noIf the first parameter is a, it receives two values
f(extra=1)No, unless extra is a parameterUnexpected keyword argument

Every one of these binding failures raises TypeError, and it raises before any line of the body executes. A PCEP item may ask whether the code runs at all, or which exception class best describes the failure.

The ordering rule is strict: positional arguments must precede keyword arguments in a call. Mixing them the other way is a SyntaxError (e.g. f(a=1, 2)), which differs from the runtime TypeError you get from a duplicate or unexpected keyword. Knowing whether a snippet fails at parse time or call time is a recurring distinction.

Default values and the required-before-default rule

A parameter with a default becomes optional for the caller. Crucially, every parameter with a default must come after all required parameters in the header, or you get a SyntaxError.

def total(price, tax=0.10, discount=0):
    return price + price * tax - discount

print(total(100))            # uses both defaults -> 110.0
print(total(100, discount=5)) # default tax, override discount -> 105.0

Defaults do not make earlier required parameters optional. For def f(a, b=2), calling f() still raises TypeError because a is unfilled; f(3) works and fills b from its default.

The mutable-default trap. Default expressions are evaluated once, when the def runs, not on each call. A mutable default such as [] is shared across calls:

def collect(item, bag=[]):
    bag.append(item)
    return bag

print(collect(1))  # [1]
print(collect(2))  # [1, 2]  <- same list reused

The safe entry-level habit is to default to immutable values (numbers, strings, booleans, or None) and build the list inside the body.

Matching crowded calls by hand

When a call mixes positional and keyword arguments, write a binding table.

def mix(a, b=10, c=20):
    return a + b * c

print(mix(2, c=3))
ParameterBound valueSource
a2positional
b10default
c3keyword

The expression becomes 2 + 10 * 3. Following operator precedence (multiplication first), that is 2 + 30 = 32.

TypeError patterns to memorize

Watch for these recurring failures:

  • Too few required arguments (power(2) for a two-parameter function).
  • Too many positional arguments.
  • The same parameter given twice, once positionally and once by keyword.
  • An unexpected keyword the function never declared.
  • A positional argument placed after a keyword argument.

When a snippet fails before its body starts, argument binding is almost always the cause. Trace the call header before tracing the body, and decide whether the error is SyntaxError (mis-ordered/keyword syntax) or TypeError (binding mismatch).

Arbitrary arguments: *args and **kwargs

PCEP-30-02 expects you to recognize, not necessarily design with, the two collecting parameters. A parameter written *args gathers any extra positional arguments into a tuple; a parameter written **kwargs gathers any extra keyword arguments into a dictionary.

def tally(first, *args, **kwargs):
    return first, args, kwargs

print(tally(1, 2, 3, mode='fast'))

The output is (1, (2, 3), {'mode': 'fast'}). first takes the single required positional 1; the leftover positionals 2 and 3 land in the tuple args; the keyword mode='fast' lands in the dict kwargs. Knowing the container types (tuple for *, dict for **) is the most testable fact here.

FormCollectsResulting type
*argsextra positional argumentstuple
**kwargsextra keyword argumentsdict

Order in the header is fixed: ordinary parameters, then *args, then keyword-only parameters, then **kwargs. An empty call such as tally(9) simply yields (9, (), {}): the tuple and dict are present but empty, never None. A common distractor claims args is a list; it is always a tuple. Another distractor claims missing extras raise an error; they do not, the containers are just empty.

Why this matters for tracing: when a header contains * or **, count the required parameters first, then sweep the remaining positionals into the tuple and the remaining keywords into the dict. Get the partition right and the output follows directly.

Test Your Knowledge

Which statement best describes the difference between parameters and arguments?

A
B
C
D
Test Your Knowledge

For def f(a, b=4, c=5): return a + b + c, what does f(1, c=2) return?

A
B
C
D
Test Your Knowledge

Why is def collect(item, bag=[]): bag.append(item); return bag risky across multiple calls?

A
B
C
D