DSM
60 XP
25 minsIntermediate60 XP

*args and **kwargs

print('a') works. So does print('a', 'b', 'c', 'd') — same function, any number of arguments. How? print's signature doesn't list forty parameters; it collects whatever arrives. Today you learn the two stars that make that possible, and read framework code without flinching.

What you'll learn

  • Write functions accepting any number of positional args with *args
  • Collect arbitrary keyword options with **kwargs
  • Unpack sequences and dicts INTO calls with * and **
  • Order signature parts correctly: required, *args, defaults, **kwargs
  • Read and write the pass-through wrapper pattern

What

*args collects any extra positional arguments into a tuple; **kwargs collects any extra keyword arguments into a dictionary. The same stars, used at a CALL site, do the reverse: unpack a sequence or dict into individual arguments.

Why

Variable-length signatures power the tools you already use — print, max, str.format — and the wrapper pattern (accept anything, pass it along) is how decorators, logging layers, and framework callbacks work. You'll read *args/**kwargs in every serious codebase this year.

Where it's used

Utility functions taking any number of inputs, wrapper/passthrough functions, plotting calls forwarding style options, and unpacking config dicts into API calls.

Where this runs in production

Python coreprint() itself

print(*objects, sep=' ', end='\n') — the signature you've called a thousand times is *args plus keyword defaults. max() and min() work the same way.

Weights & BiasesExperiment logging wrappers

wandb-style loggers wrap training functions with def wrapper(*args, **kwargs) so any model's fit call can be intercepted, timed, and forwarded unchanged.

PlotlyStyle forwarding

Chart helpers accept **kwargs and forward them to the underlying trace constructor — one wrapper supports every styling option without redeclaring fifty parameters.

Theory

The core ideas, in plain language.

In a signature, *args means 'collect all remaining positional arguments into a tuple named args'. The name is convention — *values works identically — but args is what every codebase uses. Inside the body, args is a plain tuple: loop it, len() it, index it.
def average(*values):
    if len(values) == 0:
        return 0.0
    return sum(values) / len(values)

print(average(4, 8))          # 6.0
print(average(1, 2, 3, 4, 5)) # 3.0
print(average())              # 0.0

One signature, any arity. Callers pass naked values — no list-building ceremony. The empty-call guard is the same discipline as every accumulator: decide what zero inputs means.

Analogy: The mail tray and the labeled pigeonholes

A office mailroom has named pigeonholes for known recipients (regular parameters), one open tray for any unaddressed mail (*args — it all piles in, in arrival order), and a box of labeled envelopes for anything addressed to names without pigeonholes (**kwargs — kept WITH their labels). Nothing is refused; everything lands somewhere predictable.

Key Concept
**kwargs: keywords become a dict

**kwargs collects extra keyword arguments into a dictionary: call f(color='red', width=2) and inside, kwargs is {'color': 'red', 'width': 2}. You'll study dictionaries deeply next module — for now: kwargs['color'] reads a value, 'color' in kwargs tests presence, and .get('color', 'black') reads with a fallback. That's enough to use the pattern.

Signature order is fixed and Python enforces it: regular parameters, then *args, then keyword-default parameters, then **kwargs. def report(title, *rows, sep=' | ', **options) accepts one required value, any number of extra positionals, an optional named separator, and any other named options.
Key Concept
The pass-through wrapper

def wrapper(*args, **kwargs): ...do something extra...; return real_function(*args, **kwargs) — collect EVERYTHING, forward EVERYTHING. The wrapper works for any function with any signature, which is why timing, logging, retry, and caching layers are all written this shape. When you meet decorators later, this is the entire trick.

Watch out
Flexibility is a cost, not a free lunch

A signature of (*args, **kwargs) tells readers NOTHING about what the function actually needs — no editor autocomplete, no loud arity errors, typos in option names silently vanish into kwargs. Use explicit parameters when you know the inputs; reach for stars only when genuine variability (any count, forwarding) is the point. A concrete tell: if the first line of your body is args[0], you wanted a named parameter.

Visual Learning

See the concept, then explore it.

Where each argument lands

log('save', 'ok', 200, user='ada', retry=True) hits def log(event, *details, **context). Click each node.

Worked Examples

Watch it built up, one line at a time.

Very Easy*args collects positionals

A checkout that totals any number of item prices.

Step 1 of 2

prices is a tuple of whatever arrives. sum() doesn't care whether it gets 2 values or 20.

Code
01def basket_total(*prices):
02 return round(sum(prices), 2)
Practice Coding

Your turn — write the code.

Your task

Build a scoreboard formatter. Define record_game(winner, *scores, **details) that returns a summary line: the winner's name, the number of rounds (len of scores), the total score, plus any extra details appended as key=value pairs. Produce the two outputs shown.

Expected output
Mia won 3 rounds, 30 points
Kai won 2 rounds, 24 points | venue=online, season=3

Write your solution in the editor on the right, then hit Run.

Exercises

Prove it. Reach 80% to complete the lesson.

Mastery Gate0% / 80% required
Easy0/2 solved

Inside def f(*args):, what TYPE is args?

def f(a, *rest): ... called as f(1, 2, 3, 4) — what is rest?

Medium0/3 solved

point = [3, 4] and def dist(x, y): ... — which call spreads the list into x and y?

ScenarioA teammate calls a **kwargs-based charting helper with colour='red' (British spelling). No error is raised — the chart just silently renders in the default black. The same typo against an explicit `color='black'` parameter would have raised TypeError immediately.

What does this illustrate about **kwargs?

Define longest(*words) returning the longest string passed (first wins ties). Print longest('pandas', 'numpy', 'sklearn') and longest('a', 'bc'). Expected: sklearn bc

Variadic signature:Uses *words — callers pass naked strings, not a list
Find-extreme pattern:Initializes from the data and updates on strictly-greater
Hard0/2 solved

Which signature is INVALID?

You're given def send(to, subject, priority='normal'): return f'{to} <- {subject} [{priority}]'. Build msg = {'to': 'ops@acme.io', 'subject': 'disk alert', 'priority': 'high'} and call send using ** unpacking. Print the result. Expected: ops@acme.io <- disk alert [high]

Dict unpacking:The call must use **msg, not manual key access
Keys match parameters:Each dict key lands in its same-named parameter
Complete 80% more exercises to unlock.
Interview Prep

How this shows up in real interviews.

What are *args and **kwargs, and what do the stars do at call sites?

Show model answer

In a signature, *args collects surplus positional arguments into a tuple and **kwargs collects surplus keyword arguments into a dict — the names are pure convention; the stars are the syntax. At a call site the stars invert: *sequence spreads its elements as positional arguments and **dict spreads its items as keyword arguments, with keys matching parameter names. So the same two symbols are 'pack' in a def and 'unpack' in a call. The canonical demonstration is the pass-through wrapper — def wrapper(*args, **kwargs): return f(*args, **kwargs) — which collects anything and forwards it unchanged, and is the mechanical basis of decorators, logging shims, and retry layers.

When is (*args, **kwargs) the RIGHT signature, and when is it a design smell?

Show model answer

Right: when variability is the point. Genuine any-count functions (sum-like utilities, print), forwarding wrappers that must fit every wrapped signature, and adapters passing options through to an underlying library (a chart helper forwarding **style to matplotlib). Smell: when the function actually requires specific inputs but hides them — a body starting with args[0], args[1] wanted named parameters; readers get no autocomplete, no arity errors, and misspelled options silently vanish into kwargs instead of raising TypeError. My review heuristic: stars for forwarding and true variadics, explicit names for everything the function itself consumes — and when both occur, consume by name and forward the rest.

Explain the full parameter ordering rule and why Python enforces it.

Show model answer

The canonical order is: regular positional parameters, then *args, then keyword-default parameters, then **kwargs — e.g. def report(title, *rows, sep=', ', **options). The order exists because binding must be unambiguous: positionals fill named slots left to right, *args must mark where 'the rest of the positionals' begins, anything after *args can only arrive by name (which is why parameters there are effectively keyword-only), and **kwargs sweeps up remaining names so it must be last. A worthwhile aside: a bare * in a signature — def f(data, *, strict=False) — is the same mechanism used purely to FORCE keyword-only options, which is how modern libraries prevent unreadable positional flag calls.

Common Mistakes to Avoid

1) Wrong zone order in a signature — **kwargs before *args is a SyntaxError. 2) Indexing args[0]/args[1] for required inputs — declare real parameters instead. 3) Forgetting the stars when forwarding: f(args, kwargs) passes a tuple and a dict as TWO arguments; f(*args, **kwargs) forwards the originals. 4) Expecting typo protection from **kwargs — unknown names are swallowed silently. 5) Passing a list where naked values are expected: average([1,2,3]) puts one list in the tuple; average(1,2,3) or average(*nums) is what you meant. 6) Mutating the kwargs dict then forwarding it, surprising the inner function.

Ask the AI Tutor

Try these prompts in the AI Tutor panel: • 'Quiz me: for each of these six calls, what lands in args and kwargs?' • 'Show me a wrapper that logs, then forwards, any function call.' • 'When should I use explicit parameters instead of stars? Critique this signature.' • 'Demonstrate * and ** unpacking with a config-dict example.' • 'Interview mode: ask me the parameter ordering rules and grade my answer.'

Glossary

*args — collects surplus positional arguments into a tuple (name conventional). **kwargs — collects surplus keyword arguments into a dict. Packing — the collecting behavior in a signature. Unpacking — the spreading behavior at a call site (*sequence, **dict). Variadic function — one accepting a variable number of arguments. Pass-through wrapper — a function that collects everything and forwards it to another. Keyword-only parameter — declared after *args (or a bare *), passable only by name. .items() — dict method yielding (key, value) pairs. func.__name__ — a function object's own name string.

Recommended Resources

• Docs: 'Arbitrary Argument Lists' and 'Unpacking Argument Lists' in the official Python tutorial. • Read: the signature of print() in the built-ins docs — *objects, sep, end, and you now know why it reads that way. • Practice: write describe(*nums, **options) returning min/max/mean with an optional decimals= option, then call it by unpacking a list and a dict. • Next in DSM: some functions are so small they don't deserve a name — Lambda Functions covers Python's one-expression anonymous functions and where they genuinely belong.

Recap

✓ *args packs surplus positionals into a tuple; **kwargs packs surplus keywords into a dict. ✓ Signature zones in fixed order: regular, *args, keyword defaults, **kwargs. ✓ At call sites the stars invert: *sequence and **dict spread data into arguments. ✓ The pass-through wrapper — collect everything, forward everything — powers logging, timing, and (later) decorators. ✓ Stars trade safety for flexibility: no arity errors, silent option typos — use real parameters when inputs are known. ✓ Parameters after *args are keyword-only, a feature libraries use deliberately. Next up: Lambda Functions. You've built full functions with def — next, the one-line anonymous kind you'll pass to sorted(), and the judgment call of when NOT to use them.

scratchpad — preview this lesson's challenge anytime
scoreboard.pyPython
Ready
Output

Run your code to see the output here.