skip to content

Parameters and Arguments

How a Python signature declares what a call may pass, and how the interpreter binds the arguments it receives. This is where nearly every "why did this raise TypeError?" interview question lives.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

21

What do *args and **kwargs collect in a Python function definition?

level: juniorimportance: must knowfreq 82%

answer

  1. Two stars, two containers
  2. One bucket by position, one by name
  3. Fixed types, empty rather than absent
  4. tuple for *args, dict for **kwargs

basics

~10 s

*args collects the leftover positional arguments into a tuple; **kwargs collects the leftover keyword arguments into a dict. Both types are fixed, and both are empty rather than None when nothing extra is passed.

solid answer

~40 s

In a `def`, the single star and double star declare **variadic parameters**. Any positional argument not taken by a declared parameter is packed into a `tuple` bound to `args`; any keyword argument whose name matches no declared parameter is packed into a `dict` bound to `kwargs`. The types never vary: on a call with nothing extra they are `()` and `{}`, never `None`. The names are pure convention — `def f(*items, **options)` is identical. A signature may hold at most one of each, `*args` after the ordinary parameters and `**kwargs` last of all; breaking that is a `SyntaxError`. `kwargs` keys must be strings, and since Python 3.6 (PEP 468) it preserves the order the keywords were written at the call site.

code

python · 5 lines
python
def show(*args, **kwargs):
    return type(args).__name__, args, type(kwargs).__name__, kwargs

print(show(1, 2, mode="fast"))
print(show())

go deeper

for a junior

Be ready to state the two types instantly, without hedging: *args is a tuple, **kwargs is a dict, and both are empty containers rather than None on a bare call. Then show it in three lines of code.

for a middle

Explain the mechanics behind the types: how the declared parameters are filled first and the leftovers packed, that at most one of each may appear with **kwargs last, that a violation is a compile-time SyntaxError, and that kwargs keys are strings kept in call order.

for a senior

Show judgement about when a variadic signature is warranted at all. An interviewer expects you to name what it costs — no arity check, no editor help, no useful static typing for callers — and to say which boundaries in your code deserve real parameters instead.

for a principal

Own the API-design angle: a permissive signature is a contract you cannot tighten later without breaking callers. Be ready to argue where variadics belong in a shared library and where a named, evolvable parameter list serves the organisation better.

### The two collectors A Python call site produces exactly two kinds of argument: **positional** arguments, written by position (`f(1, 2)`), and **keyword** arguments, written `name=value` (`f(mode="fast")`). A function definition binds those to its declared parameters. Two extra parameter forms exist to catch whatever the declared parameters did not take: * `*args` is a **variadic positional** parameter. Every positional argument left over after the declared parameters are filled is packed into a **`tuple`** and bound to that name. * `**kwargs` is a **variadic keyword** parameter. Every keyword argument whose name matches no declared parameter is packed into a **`dict`** and bound to that name. The stars are the syntax; `args` and `kwargs` are only a very strong convention. `def f(*items, **options)` behaves identically, and inside the body you use the names you chose. ### The types are fixed, and never `None` This is the point interviewers actually probe. `args` is *always* a `tuple` and `kwargs` is *always* a `dict`, on every call, including a call that supplies nothing extra — then they are the empty tuple `()` and the empty dict `{}`. Code that writes `if args is None` is testing a state that cannot occur; the real test is `if args:` or `if not kwargs:`. Because `args` is a tuple it is immutable: you can index and iterate it, but you cannot `append` to it. If you need to adjust the collected arguments before passing them on, build a `list` from it. `kwargs`, being a `dict`, is mutable, and it is a **fresh dict created for that call** — popping a key out of it does not disturb any mapping the caller happened to unpack into the call. ### What `kwargs` may contain Keyword argument names are identifiers, so the ordinary way to fill `kwargs` produces identifier-like string keys. The keys must be strings: passing a mapping with a non-string key raises `TypeError: keywords must be strings`. Strings that are *not* valid identifiers can still get in through a mapping — `f(**{"a-b": 1})` binds `{"a-b": 1}` into `kwargs` — which is why treating `kwargs` as a plain dict of strings, rather than assuming every key is a legal attribute name, is the safe habit. Since Python 3.6 (PEP 468) `kwargs` preserves the order in which the keyword arguments were written at the call site, and from 3.7 that ordering is a language guarantee for every `dict`. So `f(b=1, a=2)` yields `{'b': 1, 'a': 2}`, not an alphabetised or arbitrary mapping. ### Where they may appear A signature may carry **at most one** `*args` and **at most one** `**kwargs`. `*args` comes after the ordinary parameters; `**kwargs` must be the **last** parameter in the definition. Writing a second star parameter, or putting anything after the double-star one, is a `SyntaxError` raised at compile time, not a runtime error — `def f(*a, *b)` reports "* argument may appear only once", and `def f(**a, b)` reports "arguments cannot follow var-keyword argument". Note also what a variadic parameter does *not* accept. `def f(**kw)` declares no positional parameters at all, so `f(1)` raises `TypeError: f() takes 0 positional arguments but 1 was given`. The double star collects keywords only; the single star collects positionals only. They are two separate buckets, and an argument goes into exactly one of them. ### Why they exist Three recurring jobs: 1. **Genuinely variadic APIs** — a function that formats, joins, sums or dispatches over "as many as you like" of something. 2. **Forwarding** — a wrapper that must relay a call to another function without restating that function's parameter list. 3. **Open option sets** — a boundary that accepts a bag of settings whose membership it does not want to enumerate. ### The cost A variadic signature is maximally permissive, and permissiveness is not free. `def f(*args, **kwargs)` tells a reader, an editor and a static type checker nothing about what the function actually takes; the useful arity error moves from the call site into the body or into a callee one frame deeper. And because `**kwargs` has a home for *every* keyword name, a caller's typo is no longer an error — it is simply an unread entry in a dict. Use variadics where the argument set is genuinely open, and declare real parameters everywhere else.

  • Are the names args and kwargs required by the language?
    No. The stars are the syntax; the names are convention. `def f(*items, **options)` behaves exactly the same, and inside the body you use whichever names you chose. Following the convention still matters for readability, because every reader recognises it instantly.
  • Can you append to args inside the function body?
    No — `args` is a `tuple`, so it is immutable. Build a `list` from it if you need to adjust the collected positional arguments before passing them on. `kwargs` is a `dict` and is mutable, and it is a fresh dict created for that call, so popping from it never disturbs a mapping the caller unpacked into the call.
  • What happens if you call a function declared as def f(**kw) with a positional argument?
    It raises `TypeError: f() takes 0 positional arguments but 1 was given`. The double star collects keyword arguments only; it declares no positional parameters at all. The two stars fill two separate buckets, and an argument lands in exactly one of them.

A parcel sorter with two bins: unlabelled parcels go into a sealed tray in arrival order (the tuple), and labelled ones go into a pigeonhole rack keyed by name (the dict).

saying these in an interview costs you the question

  • Says *args is a list you can append to
  • Thinks **kwargs is a list of name/value pairs
  • Claims args or kwargs is None when nothing extra is passed
  • Believes the language requires the names args and kwargs
  • Thinks a signature may declare two *args parameters
  • Assumes **kwargs can hold non-string keys

context

open as a page

How do you write a Python function with an optional list parameter that starts empty each call?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Give the parameter a default of None and build the list inside the body when the caller omitted it. Writing items=[] creates one list while the def statement runs, and every call that omits the argument shares that same list.

open as a page

In Python, why does appending to a list parameter change the caller's list while rebinding that parameter does not?

level: juniorimportance: must knowfreq 80%

basics

~20 s

A call binds the parameter name to the same object the caller passed; nothing is copied. Assigning to that name repoints only the function's local name, while calling a method like append changes the one shared object.

open as a page

What does the Python call f(*a, *b, **d1, **d2) pass to f?

level: juniorimportance: must knowfreq 55%

basics

~20 s

Both iterables are flattened into one positional argument list, in source order, and both mappings are merged into the keyword arguments. Python 3.5 (PEP 448) allowed more than one star and double-star unpacking per call.

open as a page

What does a bare `*` in a Python function signature such as `def f(a, *, b)` do?

level: juniorimportance: must knowfreq 55%

basics

~10 s

It closes the positional part of the signature. Every parameter written after the bare * is keyword-only, so the caller must pass it by name: f(1, b=2) works and f(1, 2) raises TypeError.

open as a page

Why does `def add(x, bucket=[])` accumulate values across separate calls in Python?

level: middleimportance: must knowfreq 82%

basics

~20 s

Python evaluates the empty-list default once, while the def statement runs, and stores that one list on the function object. Every call that omits bucket appends to that same list, so values from earlier calls are still there.

open as a page

What do inspect.Parameter.kind values tell you about a callable's parameters?

level: middleimportance: must knowfreq 45%

basics

~10 s

Each Parameter object from inspect.signature() carries a kind saying how that parameter may be supplied: POSITIONAL_ONLY, POSITIONAL_OR_KEYWORD, VAR_POSITIONAL, KEYWORD_ONLY or VAR_KEYWORD. The name alone never tells you that.

open as a page

Why does {**d1, **d2} merge a shared key silently while f(**d1, **d2) raises TypeError?

level: middleimportance: must knowfreq 55%

basics

~20 s

A dict display only builds a dict, so a repeated key keeps the last value written. A call binds keyword arguments instead, and binding the same parameter name twice raises TypeError: got multiple values for keyword argument.

open as a page

What does inspect.signature() return, and how do you read a function's parameters from it?

level: juniorimportance: should knowfreq 42%

basics

~10 s

inspect.signature() returns a Signature object. Its .parameters attribute is an ordered mapping from parameter name to an inspect.Parameter, and each Parameter carries .kind, .default and .annotation. Printing the Signature renders the call form.

open as a page

How does the * in a Python def differ from the * in a call like f(*args)?

level: middleimportance: should knowfreq 60%

basics

~20 s

In a def the star packs: leftover arguments are collected into a tuple or dict. In a call the star unpacks: the iterable or mapping is spread back out into separate arguments. Chaining both forwards a whole call.

open as a page

In Python, why does `items += [x]` inside a function affect the caller's list but `items = items + [x]` does not?

level: middleimportance: should knowfreq 55%

basics

~20 s

Augmented assignment tries the in-place method first. A list has one, and it extends the existing object, so the caller sees the change. Plain + builds a new list and the assignment repoints only the local name.

open as a page

Why does [*records, *records] hold each item once when records is a generator object?

level: middleimportance: should knowfreq 30%

basics

~20 s

A star unpacking iterates its operand to exhaustion. A generator object is a one-shot iterator, so the first unpacking drains it and the second finds nothing left. Materialise it as a list, which can be iterated again.

open as a page

What does the `/` marker mean in a Python signature like `def f(a, b, /, c)`?

level: middleimportance: should knowfreq 40%

basics

~10 s

Every parameter before the / is positional-only: callers may pass it by position but never by name. f(1, 2, 3) works; f(a=1, b=2, c=3) raises TypeError. Python 3.8 added the syntax via PEP 570.

open as a page

Why does calling `def f(a, b)` as `f(1, a=2)` raise a TypeError?

level: middleimportance: should knowfreq 45%

basics

~20 s

Positional arguments bind first, left to right, so the 1 already fills a. The keyword a=2 then asks for a slot that is taken, and Python raises TypeError: f() got multiple values for argument 'a'.

open as a page

Why does a function taking **kwargs silently ignore a misspelled keyword, and how do you stop it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

An unexpected-keyword TypeError fires only when no parameter can hold the name, and **kwargs holds every name. If the function reads the keys it wants and drops the rest, the typo becomes an unread dict entry. Drain the leftovers and raise.

open as a page

When is None a bad sentinel for an omitted Python argument, and what do you use instead?

level: seniorimportance: should knowfreq 34%

basics

~20 s

None fails as a sentinel when None is itself a legal value for the parameter: the function can no longer tell an omitted argument from an explicit None. Use a private module-level object such as _MISSING = object(), tested with is.

open as a page

A Python service stores the list a caller passed to add_order(items), and its memory climbs all day. What is wrong at that boundary?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The call copied nothing, so the service stored the caller's own list object. The caller keeps appending to it, so the stored order silently grows and nothing can be reclaimed. Take a snapshot at the boundary instead.

open as a page

Using inspect.signature, how do you tell whether a callable accepts a given keyword argument?

level: seniorimportance: should knowfreq 34%

basics

~10 s

Walk inspect.signature(func).parameters: if any parameter has kind VAR_KEYWORD the callable accepts any keyword; otherwise the name must be present with kind POSITIONAL_OR_KEYWORD or KEYWORD_ONLY. POSITIONAL_ONLY does not count.

open as a page

When is {**base, **overrides} the wrong way to layer configuration dictionaries in Python?

level: seniorimportance: should knowfreq 38%

basics

~10 s

It is wrong whenever the layers nest, whenever precedence should follow something other than operand order, or whenever the result must not share objects with its sources. The merge is shallow, positional and eager.

open as a page

How do `/` and `*` markers stop a Python signature from making promises you cannot keep?

level: seniorimportance: should knowfreq 35%

basics

~20 s

A positional-or-keyword parameter promises callers both a position and a name. / withdraws the name so you can rename freely; * withdraws the position so you can add and reorder options. Both turn future mistakes into TypeError.

open as a page

When do inspect.getfullargspec() and inspect.signature() disagree about a callable?

level: seniorimportance: nice to knowfreq 16%

basics

~20 s

getfullargspec returns a flat FullArgSpec named tuple with no positional-only field, defaults aligned to the tail of args, and the bound first parameter of a method still listed. inspect.signature reports named Parameter objects and drops it.

open as a page