The difference between *args and **kwargs in Python is what they collect. *args gathers any extra positional arguments into a tuple, while **kwargs gathers any extra keyword (name=value) arguments into a dictionary:
def demo(*args, **kwargs):
print(args, kwargs)
demo(1, 2, color="red") # (1, 2) {'color': 'red'}
Version check: Python 3.12.5, Windows Command Prompt. The output blocks are copied straight from those runs.
In this tutorial, we’ll cover each one alone, both together, unpacking into calls, decorators and the common errors.
What does *args do in a Python function?
Put a single star in front of a parameter name and Python packs every leftover positional argument into it. The name args is only a convention; the star is what matters. Here’s an order total that takes any number of prices:
def order_total(*prices):
print("prices =", prices, "->", type(prices).__name__)
return round(sum(prices), 2)
print(f"${order_total(19.99, 5.49, 3.75):.2f}")
print(f"${order_total(120.00):.2f}")
print(f"${order_total():.2f}")
Output:
prices = (19.99, 5.49, 3.75) -> tuple
$29.23
prices = (120.0,) -> tuple
$120.00
prices = () -> tuple
$0.00

prices is always a tuple, even when it’s empty.You can loop over or index the tuple, but not change it. If you’re still getting comfortable with the def line, start with how to define a function in Python.
What does **kwargs do in a Python function?
Two stars collect every keyword argument that doesn’t match a named parameter, as a dict in the order you wrote them:
def make_profile(name, **details):
print("details =", details, "->", type(details).__name__)
profile = {"name": name}
profile.update(details)
return profile
print(make_profile("Megan Bowen", city="Seattle", state="WA", plan="Pro"))
print(make_profile("Alex Wilber"))
Output:
details = {'city': 'Seattle', 'state': 'WA', 'plan': 'Pro'} -> dict
{'name': 'Megan Bowen', 'city': 'Seattle', 'state': 'WA', 'plan': 'Pro'}
details = {} -> dict
{'name': 'Alex Wilber'}

It’s a plain dict, so you can use any dict method on it. I merged it into the profile with update(); the Python dictionary update() method shows more ways to use it.
*args vs **kwargs: key differences at a glance
| Feature | *args | **kwargs |
|---|---|---|
| Collects | Extra positional arguments | Extra keyword arguments |
| Type inside the function | tuple | dict |
| How callers pass values | f(1, 2, 3) | f(a=1, b=2) |
| Read values by | Index or loop | Key, .get() or .items() |
| Empty when nothing extra is passed | () | {} |
Neither one is a replacement for normal parameters with defaults. When you know the options in advance, name them; Python functions with optional arguments covers that approach.
How do you use *args and **kwargs together?
You can use both in one function, but the order is fixed: regular parameters, then *args, then keyword-only parameters, then **kwargs last. Anything after *items can only be passed by name:
def place_order(customer, *items, discount=0.0, **shipping):
subtotal = sum(price for _, price in items)
total = subtotal * (1 - discount)
print(f"{customer}: {len(items)} items, total ${total:.2f}")
print(" items: ", items)
print(" shipping:", shipping)
place_order("Adele Vance", ("Desk lamp", 34.99), ("Notebook", 6.50),
discount=0.10, city="Austin", state="TX", ship_date="2026-11-03")
try:
compile("def bad(**kwargs, *args):\n pass\n", "orders.py", "exec")
except SyntaxError as err:
print("SyntaxError:", err.msg)
Output:
Adele Vance: 2 items, total $37.34
items: (('Desk lamp', 34.99), ('Notebook', 6.5))
shipping: {'city': 'Austin', 'state': 'TX', 'ship_date': '2026-11-03'}
SyntaxError: arguments cannot follow var-keyword argument

**kwargs first is rejected before the code runs.Command-line scripts follow a similar idea with flags and values, which using the Python main function with arguments walks through.
How do you unpack a list or dict into a function call with * and **?
At the call site the stars work in reverse: * spreads a list or tuple into positional arguments, and ** spreads a dict into keyword arguments:
def ship_label(name, city, state, zip_code):
return f"{name}, {city}, {state} {zip_code}"
row = ["Lee Gu", "Denver", "CO", "80202"]
print(ship_label(*row)) # list -> positional arguments
address = {"name": "Diego Siciliani", "city": "Boston", "state": "MA", "zip_code": "02134"}
print(ship_label(**address)) # dict -> keyword arguments
defaults = {"plan": "Basic", "seats": 1, "billing": "monthly"}
print({**defaults, "seats": 5}) # ** also merges dicts
Output:
Lee Gu, Denver, CO 80202
Diego Siciliani, Boston, MA 02134
{'plan': 'Basic', 'seats': 5, 'billing': 'monthly'}
The dict keys must match the parameter names exactly, or you’ll get a TypeError. For more on the single-star version, see passing a tuple as an argument to a function in Python and unpacking a tuple in Python.
Pass *args and **kwargs through a decorator
The most common real-world use I see is a wrapper that accepts anything with *args, **kwargs and forwards it untouched:
import functools
def log_call(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"{func.__name__} args={args} kwargs={kwargs}")
return func(*args, **kwargs) # pass everything through unchanged
return wrapper
@log_call
def sales_tax(amount, rate=0.0825):
return round(amount * rate, 2)
print(f"${sales_tax(250.00):.2f}")
print(f"${sales_tax(99.99, rate=0.06):.2f}")
print(f"${sales_tax(amount=40.00):.2f}")
Output:
sales_tax args=(250.0,) kwargs={}
$20.62
sales_tax args=(99.99,) kwargs={'rate': 0.06}
$6.00
sales_tax args=() kwargs={'amount': 40.0}
$3.30

Decorators get their own deep dive in Python class decorators with examples. The same pass-through pattern shows up in subclasses that call super().__init__(*args, **kwargs), covered in Python class constructors with parameters.
Common *args and **kwargs errors and how to fix them
Most mistakes come down to an argument landing in the wrong place. You’ll likely meet these three TypeError messages:
def greet(name, **kwargs):
return f"Hi {name}"
def order_total(*prices):
return sum(prices)
def ship(city, state):
return f"{city}, {state}"
for call in (lambda: greet("Megan", name="Alex"),
lambda: order_total(prices=5),
lambda: ship(*["Austin", "TX", "78701"])):
try:
call()
except TypeError as err:
print("TypeError:", err)
Output:
TypeError: greet() got multiple values for argument 'name'
TypeError: order_total() got an unexpected keyword argument 'prices'
TypeError: ship() takes 2 positional arguments but 3 were given
- Multiple values: a keyword repeats a named parameter. Rename one.
- Unexpected keyword:
*argscan’t takename=value. Pass by position. - Too many positional arguments: the unpacked list is too long. Slice it or add
*args.
Once both stars feel natural, these function guides build on them:
- Use lambda functions in Python
- Built-in functions in Python
- The map() function in Python
- Return multiple values from a function in Python
Frequently asked questions
What is the difference between *args and **kwargs?
*args collects extra positional arguments into a tuple. **kwargs collects extra keyword arguments into a dictionary.
Do I have to name them args and kwargs?
No. The names are just conventions. *prices and **options work the same way; only the stars matter.
Which comes first, *args or **kwargs?
*args always comes before **kwargs, and **kwargs must be the last parameter. The reverse order is a SyntaxError.
Can a function have only **kwargs?
Yes. def f(**kwargs) accepts only keyword arguments, and calling it with a positional value raises a TypeError.
Is the order of **kwargs preserved?
Yes. Since Python 3.6, **kwargs keeps the order in which the caller wrote the keyword arguments.
Where are *args and **kwargs documented?
See arbitrary argument lists in the official Pyt