*args and **kwargs Demystified
Sometimes a function should accept *any number* of inputs — print() does it, max() does it, and after this lesson, yours can too. The syntax looks cryptic; the idea is two sentences.
The two sentences
That's genuinely the whole concept. Watch:
def demonstrate(*args, **kwargs):
print("args:", args)
print("kwargs:", kwargs)
demonstrate(1, 2, 3, name="Aarav", age=16)Output:
args: (1, 2, 3)
kwargs: {'name': 'Aarav', 'age': 16}The star gathers leftovers into a tuple; the double star gathers leftovers into a dict. Inside the function, they're ordinary containers — loop them, index them, len() them.
A real example: add them all up
def add_all(*numbers):
total = 0
for n in numbers:
total += n
return total
print(add_all(1, 2)) # 3
print(add_all(1, 2, 3, 4, 5)) # 15
print(add_all()) # 0 — empty tuple, no problemCompare with fixed parameters — add(a, b) handles exactly two, forever. add_all(*numbers) handles zero, two, or two hundred. Flexibility is the point.
args and normal parameters can mix
Normal parameters first, then *args collects the overflow:
def introduce(greeting, *names):
for name in names:
print(f"{greeting}, {name}!")
introduce("Hello", "Aarav", "Diya", "Kabir")Output:
Hello, Aarav!
Hello, Diya!
Hello, Kabir!"Hello" went to the named parameter; everything after landed in names. The order rule: normal params → *args → kwargs**, always in that order.
Where you've already used this (twice!)
Every function you've defined with a flexible body:
def wrapper(*args, **kwargs): # from the decorators world
...And the built-ins you've called all course:
print("a", "b", "c", sep="-") # print accepts *values + **kwargs
max(4, 9, 2) # max accepts any countNow you know *how* they work — the stars gather, the function loops.
Unpacking: stars work in reverse too
The stars also spread values out at call time:
def introduce(name, age):
print(f"{name} is {age}")
info = ("Diya", 15)
introduce(*info) # unpacks the tuple into two arguments!
settings = {"name": "Kabir", "age": 16}
introduce(**settings) # unpacks the dict into keyword arguments*info turns a tuple into positional arguments; **settings turns a dict into keyword arguments. This pairing — gather with stars in the definition, spread with stars in the call — is how Streamlit and every flexible library passes settings around.
Naming: the convention
args and kwargs are just names — the stars are the magic:
def add_all(*values): # totally fine
def config(**options): # also fineBut *args, **kwargs is the universal convention — stick to it unless a better name truly helps.
Common Errors & Fixes
*args; add it if extra inputs are legal.kwargs["name"], never kwargs[name] (unless you have a variable holding "name").✅ Checkpoint
*args and **kwargs collect into? *(Tuple and dict)*introduce(*("Diya", 15)) do? *(Unpacks the tuple into two positional arguments)*Next: scope — where variables live, why they vanish, and the LEGB rule in five minutes.