In a function signature, *args collects extra positional arguments
into a tuple, and **kwargs collects extra keyword arguments into a
dict. They let a function accept a variable number of arguments. The names
are convention — only the */** matter.
def log(level, *args, **kwargs):
print(level, args, kwargs)
log("INFO", 1, 2, user="ada", id=7)
# INFO (1, 2) {'user': 'ada', 'id': 7}
args is always a tuple and kwargs always a dict. They're essential
for writing wrappers/decorators that forward arbitrary arguments through to
another callable.
Positional arguments are matched to parameters by their order.
Keyword arguments are matched by name (param=value), so order
doesn't matter among them. At the call site you can mix the two, but every
positional argument must come before any keyword argument.
def greet(name, greeting): ...
greet("Ada", "Hello") # both positional
greet(name="Ada", greeting="Hi") # both keyword (order free)
greet("Ada", greeting="Hi") # mix: positional first
greet(name="Ada", "Hi") # SyntaxError — kw before positional
Keyword arguments make calls self-documenting and let you skip over earlier parameters that have defaults. Use them for clarity on boolean flags and long argument lists.
A parameter with name=value is optional — if the caller omits it, the
default is used. Defaults are evaluated once, when the def runs, so
using a mutable default ([], {}) is a classic trap: the same object
persists across calls.
def connect(host, port=5432, timeout=30):
...
connect("db") # uses port=5432, timeout=30
connect("db", timeout=5) # override one by keyword
def bad(item, bucket=[]): # DON'T — shared list
bucket.append(item); return bucket
The safe pattern for a mutable default is bucket=None plus
if bucket is None: bucket = [] inside the body. Parameters with defaults
must come after those without.
Any parameter listed after a bare * (or after *args) is
keyword-only — it can never be passed positionally and must be named at
the call site. This forces clearer calls and prevents accidental
positional mistakes.
def make_request(url, *, timeout=30, verify=True):
...
make_request("http://x", timeout=5) # OK
make_request("http://x", 5) # TypeError — timeout is kw-only
Keyword-only parameters are great for optional flags whose meaning isn't
obvious from position (especially booleans). The lone * is just a
separator; it doesn't collect anything.
Parameters listed before a / in the signature are positional-only
(Python 3.8+) — they cannot be passed by keyword. This is useful for APIs
where the parameter name is an implementation detail you don't want callers
to depend on.
def divide(a, b, /):
return a / b
divide(10, 2) # OK
divide(a=10, b=2) # TypeError — a, b are positional-only
It also frees those names for use in **kwargs. Many built-ins (like
len, pow) are positional-only. Combined with *, a signature can have
positional-only, normal, and keyword-only sections.
A full signature follows a fixed order:
positional-only /, then normal, then *args, then keyword-only, then
**kwargs. Within each group, parameters without defaults precede those
with defaults.
def f(pos_only, /, normal, *args, kw_only, **kwargs):
...
# call-site unpacking mirrors this:
def g(a, b, c): ...
nums = [1, 2, 3]
g(*nums) # spread list into positionals
g(**{"a": 1, "b": 2, "c": 3}) # spread dict into keywords
Getting the order wrong is a SyntaxError. The *// markers partition
the signature; remember the sequence "positional-only → normal → varargs →
keyword-only → varkwargs."
The default is evaluated once at definition time and shared across all
calls. A [] or {} default therefore persists and accumulates between
calls. Use None as the sentinel and create a fresh object inside.
def add(item, bucket=[]): # BUG: one shared list
bucket.append(item); return bucket
add(1); add(2) # [1, 2] !
def add(item, bucket=None): # correct
if bucket is None: bucket = []
bucket.append(item); return bucket
Rule of thumb: never use a mutable literal as a default — default to None
and build the object in the body.
At a call, *iterable spreads items into positional arguments and
**mapping spreads into keyword arguments. It's the inverse of *args/
**kwargs in a definition. You can mix them and even use * multiple times
(3.5+).
def f(a, b, c): ...
args = (1, 2); f(*args, 3) # f(1, 2, 3)
kw = {"b": 2, "c": 3}; f(1, **kw) # f(1, b=2, c=3)
f(*[1], *[2], **{"c": 3}) # multiple unpacks
Rule of thumb: */** at the call site unpack collections into arguments;
in the signature they collect arguments.
Neither exactly — it's "pass by object reference" (call by sharing). The
function gets a reference to the same object. Mutating it (e.g.
list.append) is visible to the caller; rebinding the parameter
(x = ...) only changes the local name, not the caller's variable.
def f(lst, x):
lst.append(1) # caller sees this (mutation)
x = 99 # caller does NOT see this (rebinding)
data, n = [], 0
f(data, n) # data == [1], n == 0
Rule of thumb: mutations to the object propagate; reassigning the parameter name does not.
Yes — since Python 3.7, **kwargs is an ordinary dict and preserves the
order the keyword arguments were passed. This lets you forward or process
kwargs predictably (e.g. building HTML attributes in source order).
def tag(**attrs):
return " ".join(f'{k}="{v}"' for k, v in attrs.items())
tag(id="x", cls="y") # 'id="x" cls="y"' -> order preserved
Rule of thumb: you can rely on kwargs insertion order on modern Python.
A lone * marks the start of keyword-only parameters — everything
after it must be passed by name. It's used to force clarity at call sites,
especially for boolean flags or options.
def connect(host, *, timeout=30, retries=3):
...
connect("db", timeout=5) # OK
connect("db", 5) # TypeError: too many positional args
Rule of thumb: put * before options you want callers to name explicitly,
avoiding ambiguous positional flags.
Parameters before / are positional-only — they can't be passed by
keyword (3.8+). It mirrors many C built-ins (len, abs), lets you rename
params freely without breaking callers, and avoids name clashes with
**kwargs.
def divide(a, b, /):
return a / b
divide(10, 2) # OK
divide(a=10, b=2) # TypeError: positional-only
Rule of thumb: use / for parameters whose names are implementation details
or that must accept arbitrary keyword keys via **kwargs.
A parameter is the variable in the function definition; an argument is the actual value passed at the call site. Parameters define the interface; arguments fill it in.
def greet(name): # `name` is a parameter
...
greet("Ada") # "Ada" is an argument
Rule of thumb: parameters live in the def, arguments live in the call.
Accept *args, **kwargs and pass them straight through with *args, **kwargs. This is the standard pattern for wrappers, decorators, and
super().__init__ chains that shouldn't care about the exact signature.
def wrapper(*args, **kwargs):
log("calling")
return target(*args, **kwargs) # transparent forwarding
Rule of thumb: *args, **kwargs in and out is how you write signature-agnostic
wrappers.
Use keywords for booleans, numbers, and any value whose meaning isn't
obvious at the call site. f(True, False) is cryptic; f(verbose=True, cache=False) is self-documenting and resilient to parameter reordering.
open("f.txt", "w", buffering=1) # named buffering reads clearly
split(text, maxsplit=1) # vs split(text, 1)
Rule of thumb: pass literals (especially bare True/False/numbers) by
keyword for readability.
Once, when the def executes (definition time) — not on each call. So a
default referencing a variable captures its value at definition, and a
default like datetime.now() is frozen to one moment. Use None + compute
inside for per-call defaults.
import time
def stamp(t=time.time()): # frozen at def time
return t
stamp(); time.sleep(1); stamp() # same value both times
def stamp(t=None): # fresh each call
return time.time() if t is None else t
Rule of thumb: if a default must be recomputed per call, default to None and
build it in the body.
More Functions interview questions
More ways to practice
The self-quiz is live. Join our channel for updates, new content & tech tips.