*args & **kwargs
Reviewed & published by Brayan K
How does print() accept one argument or ten? How does a function pass along whatever it's given to another function? The answer is *args and **kwargs — Python's way of writing functions that accept a flexible, unknown number of inputs.
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Once you understand the star operators, a lot of "magic" library code suddenly reads clearly.
What You'll Learn in This Lesson
- Collecting extra positional args with *args (a tuple)
- Collecting extra keyword args with **kwargs (a dict)
- The required order of parameters in a signature
- Unpacking with * and ** at the call site
- Combining everything in one realistic function
- When NOT to use them (prefer explicit params)
1 The Problem *args Solves
Imagine writing an add function. With fixed parameters you'd have to guess how many numbers the caller might pass:
# Rigid — only ever works for exactly two numbers
def add(a, b):
return a + b
print(add(2, 3)) # 5
# print(add(2, 3, 4)) # TypeError: too many arguments!*args fixes this. The star tells Python: "gather all the leftover positional arguments into a single tuple called args":
def add(*args):
print("Received:", args) # args is a tuple
return sum(args)
print(add(2, 3)) # Received: (2, 3) -> 5
print(add(2, 3, 4, 5)) # Received: (2, 3, 4, 5) -> 14
print(add()) # Received: () -> 0Inside the function, args is just a normal tuple — you can loop over it, index it, or pass it to sum().
🏷️ **kwargs — Flexible Named Arguments
While *args catches extra positional arguments, **kwargs catches extra keyword (named) arguments and stores them in a dictionary:
def make_profile(**kwargs):
print("Got dict:", kwargs)
for key, value in kwargs.items():
print(f" {key}: {value}")
make_profile(name="Ada", role="engineer", city="London")
# Output:
# Got dict: {'name': 'Ada', 'role': 'engineer', 'city': 'London'}
# name: Ada
# role: engineer
# city: LondonEach key=value the caller supplies becomes one entry in the kwargs dictionary. This is exactly how flexible configuration functions and many web framework APIs work under the hood.
2 The Required Parameter Order
You can combine regular parameters, *args, and **kwargs — but only in one specific order. Get it wrong and Python raises a SyntaxError:
def func(positional, *args, keyword_only=..., **kwargs):
^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^^ ^^^^^^^^
1. normal 2. extra 3. named with 4. extra
params values defaults nameddef report(title, *items, sep=" | ", **meta):
print(title)
print(sep.join(str(i) for i in items))
for k, v in meta.items():
print(f"{k} = {v}")
report("Sales", 100, 250, 90, sep=", ", region="EU", year=2024)
# Output:
# Sales
# 100, 250, 90
# region = EU
# year = 2024# A flexible logging helper
def log(level, *messages, **context):
line = f"[{level.upper()}] " + " ".join(str(m) for m in messages)
if context:
extras = ", ".join(f"{k}={v}" for k, v in context.items())
line += f" ({extras})"
print(line)
log("info", "Server started")
log("warn", "High memory", "82% used", host="web-1", pid=4021)
log("error", "DB timeout", retries=3, db="orders")
# ✅ Expected output:
# [INFO] Server started
# [WARN] High memory 82% used (host=web-1, pid=4021)
# [ERROR] DB timeout (retries=3, db=orders)3 The Other Direction: Unpacking at the Call Site
The star operators work in reverse too. When you call a function, * spreads a list/tuple into positional arguments and ** spreads a dict into keyword arguments:
def greet(greeting, name):
return f"{greeting}, {name}!"
# Unpack a list into positional arguments
parts = ["Hello", "Ada"]
print(greet(*parts)) # Hello, Ada!
# Unpack a dict into keyword arguments
data = {"greeting": "Hi", "name": "Ben"}
print(greet(**data)) # Hi, Ben!
# Merge collections cleanly with unpacking
a = [1, 2]
b = [3, 4]
print([*a, *b]) # [1, 2, 3, 4]
d1 = {"x": 1}
d2 = {"y": 2}
print({**d1, **d2}) # {'x': 1, 'y': 2}🔁 The Classic Use: Forwarding Arguments
The most common professional reason to use both together is writing a wrapper that accepts anything and passes it straight through to another function — the heart of how decorators work:
def timed(func, *args, **kwargs):
print(f"Calling {func.__name__}...")
result = func(*args, **kwargs) # forward EVERYTHING
print("Done.")
return result
def area(length, width, unit="m"):
return f"{length * width} {unit}²"
# timed forwards both positional and keyword args untouched
print(timed(area, 4, 5, unit="cm"))
# Output:
# Calling area...
# Done.
# 20 cm²Because timed doesn't care what arguments area needs, it can wrap any function at all. This "accept anything, forward everything" pattern is everywhere in real Python libraries.
🧩 Reorder Challenge
These lines should define an averaging function and call it by unpacking a list. Reorder them so the output is Average: 20.0.
A) return sum(nums) / len(nums)
B) def average(*nums):
C) values = [10, 20, 30]
D) print(f"Average: {average(*values)}")B → A → C → D
def average(*nums): # B
return sum(nums) / len(nums) # A (indented body)
values = [10, 20, 30] # C
print(f"Average: {average(*values)}") # D -> Average: 20.0Why: the def header (B) must come before its indented body (A). The values list (C) is created next, then the call (D) unpacks it with *values so the three numbers arrive as separate positional args. Their average is 20.0.
🧠 Quick Recall
1. What is the type of args here?
def f(*args):
print(type(args))
f(1, 2, 3)<class 'tuple'> — *args always collects extra positional arguments into a tuple, even though you call with a star.
def f(a, b):
return a - b
print(f(*[10, 3]))7 — *[10, 3] spreads the list so a=10 and b=3, giving 10 - 3.
3. What is the result?
print({**{"a": 1}, **{"a": 9, "b": 2}}){'a': 9, 'b': 2} — merging dicts with ** lets later keys overwrite earlier ones, so a becomes 9.
📋 Star Operator Quick Reference
| Syntax | Where | Does |
|---|---|---|
| *args | Definition | Collect extra positionals → tuple |
| **kwargs | Definition | Collect extra keywords → dict |
| *list | Call site | Spread list → positional args |
| **dict | Call site | Spread dict → keyword args |
| {**a, **b} | Literal | Merge two dictionaries |
🎯 Mini Challenge: Flexible Shopping Cart
Write a checkout function that totals any number of prices and accepts optional named settings.
# 🎯 YOUR TURN: total any number of items + options
def checkout(*prices, tax=0.0, currency="$"):
subtotal = sum(prices)
total = subtotal * (1 + tax)
return f"{currency}{total:.2f}"
print(checkout(9.99, 4.50, 12.00)) # $26.49
print(checkout(9.99, 4.50, 12.00, tax=0.2)) # $31.79
print(checkout(100, tax=0.1, currency="£")) # £110.00
# Bonus: build a cart list and unpack it into the call
cart = [3.0, 7.5, 1.25]
print(checkout(*cart, tax=0.05))
# ✅ Expected output:
# $26.49
# $31.79
# £110.00
# $12.34Lesson complete — your functions are flexible now!
You can collect extra arguments with *args and **kwargs, order parameters correctly, unpack collections at the call site, and forward arguments through wrappers. This unlocks a huge amount of real-world Python code.
Practice quiz
Inside def f(*args), what type is args?
- list
- dict
- tuple
- set
Answer: tuple. *args always collects extra positional arguments into a tuple.
Inside def g(**kwargs), what type is kwargs?
- dict
- tuple
- list
- set
Answer: dict. **kwargs collects extra keyword arguments into a dictionary.
Given def add(*args): return sum(args), what does add() return?
- None
- an error
- ()
- 0
Answer: 0. With no arguments, args is the empty tuple () and sum(()) is 0.
Given def sub(a, b): return a - b, what does sub(*[10, 3]) print?
- 13
- 7
- [10, 3]
- an error
Answer: 7. *[10, 3] spreads the list so a=10 and b=3, giving 10 - 3 = 7.
What is the result of {**{'a': 1}, **{'a': 9, 'b': 2}}?
- {'a': 9, 'b': 2}
- {'a': 1, 'b': 2}
- {'a': 10, 'b': 2}
- an error
Answer: {'a': 9, 'b': 2}. When merging dicts with **, later keys overwrite earlier ones, so a becomes 9.
Which parameter order is valid in a function signature?
- def f(**kwargs, *args)
- def f(*args, a, **kwargs)
- def f(a, *args, **kwargs)
- def f(*args, a)
Answer: def f(a, *args, **kwargs). The required order is: normal params, then *args, then keyword-only params, then **kwargs.
In a function DEFINITION, what does the * in *args do?
- Spreads a tuple into arguments
- Collects extra positionals into a tuple
- Multiplies the arguments
- Nothing
Answer: Collects extra positionals into a tuple. In a definition the star collects; at a call site it spreads. Same symbol, mirror jobs.
A parameter listed AFTER *args in a signature becomes...
- positional-only
- optional
- ignored
- keyword-only
Answer: keyword-only. Anything after *args must be passed by name, making it keyword-only.
What does [*[1, 2], *[3, 4]] evaluate to?
- [[1, 2], [3, 4]]
- [1, 2, 3, 4]
- [4, 6]
- an error
Answer: [1, 2, 3, 4]. Unpacking with * spreads both lists into one flat list [1, 2, 3, 4].
Why is *args/**kwargs ideal for writing a wrapper or decorator?
- It runs faster
- It hides the original function
- It forwards any arguments straight through to the wrapped function
- It removes default values
Answer: It forwards any arguments straight through to the wrapped function. The wrapper accepts anything and forwards it with func(*args, **kwargs), so it can wrap any function.
Continue this course
- Previous: Dictionaries
- Next: Lambda Functions — Write small anonymous functions for sorting, mapping and filtering
- Quick reference: Python cheat sheet › Functions
Frequently asked questions
What do *args and **kwargs actually mean?
They let a function accept a flexible number of arguments. *args collects any extra positional arguments into a tuple, and **kwargs collects any extra keyword arguments into a dictionary. The single star handles values passed by position; the double star handles values passed by name.
Are the names 'args' and 'kwargs' required?
No — only the stars matter. You could write *items and **options and it works identically. But args (arguments) and kwargs (keyword arguments) are such a strong convention that every Python developer recognizes them instantly, so stick with them unless a clearer name genuinely helps.
What's the correct order of parameters in a function signature?
Always: regular positional parameters, then *args, then keyword-only parameters, then **kwargs. For example def f(a, b, *args, sep=' ', **kwargs). Putting them out of order is a SyntaxError, because Python needs the catch-alls last to know where the named parameters end.
What does the star do when CALLING a function, not defining one?
It unpacks. At a call site, *some_list spreads a list into separate positional arguments and **some_dict spreads a dict into keyword arguments. So if nums = [1, 2, 3], then add(*nums) is the same as add(1, 2, 3). It's the mirror image of collecting.
When should I avoid *args and **kwargs?
When the arguments are actually known and fixed — explicit parameters are clearer and give better editor hints and error messages. Reach for *args/**kwargs when you genuinely don't know how many inputs you'll get, or when writing a wrapper/decorator that must forward whatever it receives.