By Lisa Tagliaferri and Vinayak Baranwal
In function definitions, parameters are named entities that specify which arguments a function can accept. When you need to support a variable number of arguments without fixing the signature, Python offers two special syntaxes: *args for extra positional arguments and **kwargs for extra keyword arguments. This tutorial covers how to define and call functions with *args and **kwargs, how to combine them with standard and keyword-only parameters, and how to use them in decorators, class inheritance, and type-annotated code.
*args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict.*args, then keyword-only parameters, then **kwargs. Reversing or mixing this order causes a SyntaxError.*) and double asterisk (**) are the operators that matter; the names args and kwargs are conventions only.* and ** at the call site to unpack a sequence or mapping into positional and keyword arguments when calling a function.*args and **kwargs (e.g., *args: int, **kwargs: str) apply to each element or value, not to the tuple or dict as a whole.*args/**kwargs for flexible APIs, decorators, and forwarding, and be aware of the small overhead of tuple and dict creation in hot paths.You should have Python 3 installed and a programming environment set up on your computer or server. If you do not have one set up, refer to the installation and setup guides for a local programming environment or for a programming environment on your server for your operating system (Ubuntu, CentOS, Debian, etc.).
Info: To follow along with the example code in this tutorial, open a Python interactive shell on your local system by running the python3 command. You can copy, paste, or edit the examples by adding them after the >>> prompt.
Python functions accept positional arguments (matched by position) and keyword arguments (matched by name). Variable-length argument lists are handled by *args and **kwargs, which collect any extra positional or keyword arguments into a single parameter. You write a function that adds two numbers. Later you need it to add three. Then four. With fixed parameters, every change breaks existing call sites. *args and **kwargs solve this by letting the function signature grow without breaking anything.
Standard parameters have fixed names and a fixed count. Variable arguments allow a function to accept zero or more extra positional arguments (*args) or extra keyword arguments (**kwargs) without listing each one in the signature. The following table summarizes the two variable forms.
| Feature | *args | **kwargs |
|---|---|---|
| Syntax | *args |
**kwargs |
| Data structure | tuple | dict |
| Argument type | positional | keyword |
| Iteration method | for val in args |
for k, v in kwargs.items() |
| Keyword required at call | No | Yes |
| Type annotation target | each element | each value |
Arguments in a function definition must appear in a strict order. The table below shows the legal order; violating it raises a SyntaxError.
| Position | Type | Example |
|---|---|---|
| 1 | Standard positional | arg_1, arg_2 |
| 2 | Variable positional | *args |
| 3 | Keyword-only | kw_1="default" |
| 4 | Variable keyword | **kwargs |
Warning: Placing **kwargs before *args raises a SyntaxError immediately. Python rejects the file before running a single line. For example, def f(**kwargs, *args) produces:
SyntaxError: invalid syntax
Always follow the legal ordering: standard positional, then *args, then keyword-only, then **kwargs.
Example of a valid signature combining all four:
def full_example(name, age, *args, role="user", **kwargs):
# name, age: required positional params
# *args: captures extra positional values as a tuple
# role: keyword-only param with a default
# **kwargs: captures extra keyword args as a dict
print(f"name={name}, age={age}")
print(f"extra positional args: {args}")
print(f"role={role}")
print(f"extra kwargs: {kwargs}")
full_example("Sam", 30, "extra1", "extra2", role="admin", team="infra", region="us-east")
- python3 full_example.py
Outputname=Sam, age=30
extra positional args: ('extra1', 'extra2')
role=admin
extra kwargs: {'team': 'infra', 'region': 'us-east'}
*args lets a function accept any number of extra positional arguments. They are collected into a single tuple, which you can iterate over or index.
At the interpreter level, the asterisk packs all remaining positional arguments into a tuple and binds it to the name args. You can confirm this at runtime:
def inspect_args(*args):
# Show the type and contents of args at runtime
print(type(args)) # always <class 'tuple'>
print(args) # the values as a tuple
print(args[0]) # indexing works like any tuple
inspect_args(10, 20, 30)
- python3 args_type_check.py
Output<class 'tuple'>
(10, 20, 30)
10
Because args is a plain tuple, you can index it, slice it, pass it to len(), or forward it to another function with *args.
Info: You can name these parameters anything valid. Use *args and **kwargs for general-purpose functions. Use descriptive names like *prices or **options when the collected values have a clear domain meaning; it makes the function signature self-documenting.
A function that multiplies all given numbers can be written with *args so it works for two, three, or more arguments:
def multiply(*args):
# args is a tuple of all passed positional arguments
z = 1
for num in args:
z *= num
print(z)
multiply(4, 5)
multiply(10, 9)
multiply(2, 3, 4)
multiply(3, 5, 10, 6)
- python3 lets_multiply.py
Output20
90
24
900
Without *args, adding a third argument would require changing the function signature and every call site. With *args, the function stays flexible.
**kwargs collects extra keyword arguments into a single dict. Each argument must be passed with a keyword (e.g., name="Sam").
The double asterisk in **kwargs tells Python to pack all extra keyword arguments into a dict and bind it to the name kwargs. Keys are the parameter names used at the call site; values are the corresponding values. You can confirm this at runtime:
def inspect_kwargs(**kwargs):
# Show the type and contents of kwargs at runtime
print(type(kwargs)) # always <class 'dict'>
print(kwargs) # the full dict
print(kwargs["role"]) # direct key access like any dict
print(kwargs.get("team", "unset")) # safe access with a default
inspect_kwargs(role="admin", name="Sam")
- python3 kwargs_type_check.py
Output<class 'dict'>
{'role': 'admin', 'name': 'Sam'}
admin
unset
Because kwargs is a plain dict, you can use .keys(), .values(), .items(), .get(), or any other dict method on it.
A simple way to inspect what was passed is to print kwargs:
def print_kwargs(**kwargs):
# kwargs is a dict of all passed keyword arguments
print(kwargs)
print_kwargs(kwargs_1="Shark", kwargs_2=4.5, kwargs_3=True)
- python3 print_kwargs.py
Output{'kwargs_1': 'Shark', 'kwargs_2': 4.5, 'kwargs_3': True}
Note: In Python 3.7 and later, **kwargs preserves insertion order because dict is ordered by default. CPython 3.6 also maintains insertion order as an implementation detail, but earlier Python 3 versions do not guarantee iteration order.
You can iterate over key-value pairs like any other dictionary:
def print_values(**kwargs):
for key, value in kwargs.items():
# Iterate over each keyword argument and print its name and value
print(f"The value of {key} is {value}")
print_values(my_name="Sammy", your_name="Casey")
- python3 print_values.py
OutputThe value of my_name is Sammy
The value of your_name is Casey
You can also access a specific key from kwargs with .get() and a default:
def greet_user(**kwargs):
# Access a specific key safely with .get(); supply a default if missing
name = kwargs.get("name", "stranger")
role = kwargs.get("role", "user")
print(f"Hello, {name}. Your role is {role}.")
greet_user(name="Sam", role="admin")
greet_user(name="Alex")
- python3 kwargs_access.py
OutputHello, Sam. Your role is admin.
Hello, Alex. Your role is user.
Using .get() with a default prevents a KeyError when a caller omits an optional keyword argument.
A common real-world case is a log formatter that requires a severity level but accepts any number of message parts and optional metadata fields:
def log_message(severity, *args, **kwargs):
# severity: required; controls log level
# args: any number of message parts, joined into one string
# kwargs: optional metadata fields such as user, request_id, or service
message = " ".join(str(a) for a in args)
metadata = ", ".join(f"{k}={v}" for k, v in kwargs.items())
print(f"[{severity.upper()}] {message} | {metadata}" if metadata else
f"[{severity.upper()}] {message}")
log_message("info", "User logged in")
log_message("error", "Payment failed", "retry 3 of 3", user="sam", request_id="abc123")
- python3 log_message.py
Output[INFO] User logged in
[ERROR] Payment failed retry 3 of 3 | user=sam, request_id=abc123
Calling log_message with only severity and no extras is valid; args will be an empty tuple and kwargs will be an empty dict.
The same * and ** symbols are used at the call site to unpack sequences and mappings into positional and keyword arguments. This is the inverse of collecting: you spread one sequence or dict into multiple arguments.
Prefixing an iterable with * unpacks its elements as positional arguments. Useful when you have a list or tuple and want to pass each element as a separate argument:
def greet(first, last, city):
print(f"Hello, {first} {last} from {city}.")
# Unpack list for first two args, then use a dict for the third
parts = ["Ada", "Lovelace"]
location = {"city": "London"}
greet(*parts, **location)
- python3 unpack_call.py
OutputHello, Ada Lovelace from London.
Here *parts supplies first and last, and **location supplies city. You can mix literal arguments with unpacking:
def some_args(arg_1, arg_2, arg_3):
print("arg_1:", arg_1)
print("arg_2:", arg_2)
print("arg_3:", arg_3)
args = ("Sammy", "Casey", "Alex")
some_args(*args)
- python3 some_args.py
Outputarg_1: Sammy
arg_2: Casey
arg_3: Alex
Prefixing a mapping with ** unpacks its key-value pairs as keyword arguments. The keys must match the function’s parameter names (or be accepted by **kwargs):
def some_kwargs(kwarg_1, kwarg_2, kwarg_3):
print("kwarg_1:", kwarg_1)
print("kwarg_2:", kwarg_2)
print("kwarg_3:", kwarg_3)
kwargs = {"kwarg_1": "Val", "kwarg_2": "Harper", "kwarg_3": "Remy"}
some_kwargs(**kwargs)
- python3 some_kwargs.py
Outputkwarg_1: Val
kwarg_2: Harper
kwarg_3: Remy
Two errors appear frequently when unpacking.
Error 1: Too many positional arguments:
def add(a, b):
return a + b
values = [1, 2, 3] # one element too many
add(*values)
- python3 unpack_error_positional.py
OutputTypeError: add() takes 2 positional arguments but 3 were given
Unpacking a sequence with more elements than the function has parameters raises a TypeError. Count your elements or use a slice: add(*values[:2]).
Error 2: Dict key does not match parameter name:
def greet(name, city):
print(f"Hello, {name} from {city}.")
data = {"name": "Sam", "country": "India"} # 'country' is not a parameter
greet(**data)
- python3 unpack_error_keyword.py
OutputTypeError: greet() got an unexpected keyword argument 'country'
Unpacking a dict whose keys do not match the function’s parameter names raises a TypeError. Either align the dict keys to the parameter names, or ensure the function accepts **kwargs to absorb the extras.
A decorator written without *args and **kwargs only works for functions with an exact parameter count. Apply it to any other function and it breaks. The three patterns below show how *args and **kwargs solve this in the most common real-world scenarios.
A decorator often wraps a function without knowing its signature. Using *args and **kwargs in the wrapper lets it forward any arguments to the wrapped function. Example: a simple call logger:
import functools
def log_call(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Log the function name and all arguments before calling
print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@log_call
def add(x, y):
return x + y
add(3, 7)
- python3 decorator_example.py
OutputCalling add with args=(3, 7), kwargs={}
add returned 10
10
The wrapper accepts any positional and keyword arguments, passes them through to func, and returns the result. The same pattern appears in timing, retry, and validation decorators.
When a subclass overrides __init__ and must call the parent initializer, it often forwards the same arguments. Using *args and **kwargs avoids repeating the parent’s parameter list:
class Animal:
def __init__(self, name, sound):
self.name = name
self.sound = sound
class Dog(Animal):
def __init__(self, *args, breed="unknown", **kwargs):
# Forward all positional and keyword args to the parent class
super().__init__(*args, **kwargs)
self.breed = breed
def describe(self):
print(f"{self.name} ({self.breed}) says {self.sound}")
d = Dog("Rex", "woof", breed="Labrador")
d.describe()
- python3 inheritance_example.py
OutputRex (Labrador) says woof
Here Dog adds a breed keyword-only parameter and forwards everything else to Animal.__init__. This pattern is common in frameworks and large class hierarchies.
In frameworks like Flask, a decorator registers a route and passes request data to your view function. The framework’s internal machinery uses *args and **kwargs to forward URL parameters, query strings, and other context without knowing your function’s signature in advance. Here is a minimal illustration of that pattern:
def route(path):
"""Simulate a framework decorator that registers a URL route."""
def decorator(func):
# The wrapper uses *args and **kwargs so it can forward any arguments
# the framework collects from the request to the view function
def wrapper(*args, **kwargs):
print(f"Routing {path!r} -> {func.__name__}")
return func(*args, **kwargs)
return wrapper
return decorator
@route("/users/<username>")
def user_profile(username):
return f"Profile page for {username}"
# Simulate the framework calling the view with a captured URL segment
print(user_profile("sam"))
- python3 flask_style.py
OutputRouting '/users/<username>' -> user_profile
Profile page for sam
Real Flask routes work the same way: the framework unpacks URL variables and passes them as keyword arguments to your view function, which is why view functions only declare the parameters they need.
As of Python 3.5, you can annotate the types of values collected by *args and **kwargs. The annotation describes each element or value, not the tuple or dict itself:
def process(*args: int, **kwargs: str) -> None:
# Each element in args is expected to be an int
total = sum(args)
# Each value in kwargs is expected to be a str
labels = ", ".join(kwargs.values())
print(f"Sum: {total}, Labels: {labels}")
process(1, 2, 3, unit="kg", source="sensor")
- python3 type_annotations_example.py
OutputSum: 6, Labels: kg, sensor
So *args: int means “each positional argument should be an int,” and **kwargs: str means “each keyword argument value should be a str.” Type checkers use these for static analysis; the interpreter does not enforce them at runtime.
To catch mismatches statically, run a type checker such as mypy:
- pip3 install mypy
- mypy type_annotations_example.py
If you call process("a", "b") instead of process(1, 2), mypy reports an Argument of type "str" is not assignable to parameter of type "int" error before the code runs. The interpreter will still execute it without raising an exception.
Using *args and **kwargs has a small runtime cost: Python builds a new tuple for the extra positional arguments and a new dict for the extra keyword arguments on each call. You can measure it with timeit:
import timeit
def explicit(a, b, c):
# Fixed parameters: no tuple or dict created
return a + b + c
def with_args(*args):
# *args: Python builds a tuple on every call
return sum(args)
explicit_time = timeit.timeit(lambda: explicit(1, 2, 3), number=1_000_000)
args_time = timeit.timeit(lambda: with_args(1, 2, 3), number=1_000_000)
print(f"explicit: {explicit_time:.3f}s")
print(f"with_args: {args_time:.3f}s")
- python3 perf_check.py
Outputexplicit: 0.048s
with_args: 0.071s
Over one million calls, *args adds roughly 20-50% overhead versus explicit parameters in a minimal function. For most application code this is irrelevant. For inner loops processing millions of items, prefer explicit parameters.
Info: Run perf_check.py on your own system to get accurate timings for your hardware and Python version. The relative difference matters more than the absolute values.
When you need flexibility, you have several options. The table below compares them.
| Approach | When to use |
|---|---|
*args / **kwargs |
Variable argument count, decorators, forwarding to another callable, framework-style APIs. |
| Pass a list or dict directly | Fixed structure known to the caller; you want one object (e.g., config: dict) instead of many keyword arguments. |
| Dataclasses or a single object | Many related fields with clear names and types; you want validation, defaults, and a single “options” object. |
| TypedDict | Keyword-like structure with type checking; good when the set of keys is known and you want static typing for the dict. |
Choose explicit parameters when the API is stable and the number of arguments is small. Use *args/**kwargs when the API must accept or forward arbitrary arguments.
**What is the difference between *args and kwargs in Python?
*args collects extra positional arguments into a tuple. **kwargs collects extra keyword arguments into a dict. Positional arguments are passed by position (e.g., f(1, 2)); keyword arguments are passed by name (e.g., f(a=1, b=2)).
**Do I have to name them *args and kwargs?
No. The names are conventions. The single asterisk (*) and double asterisk (**) are required; you can use *values or **options or any valid identifier.
What order must arguments appear in a Python function definition?
Standard positional parameters first, then *args, then keyword-only parameters, then **kwargs. Violating this order causes a SyntaxError.
**Can I use *args and kwargs together in the same function?
Yes. Use *args before **kwargs. The function will receive a tuple of extra positional arguments and a dict of extra keyword arguments.
How do I unpack a list or dictionary when calling a function?
Use * to unpack a sequence into positional arguments (e.g., f(*my_list)). Use ** to unpack a mapping into keyword arguments (e.g., f(**my_dict)). You can combine both in one call.
**Can I type-annotate *args and kwargs?
Yes. In Python 3.5 and later you can write *args: int and **kwargs: str. The annotation applies to each element or value, not to the tuple or dict. Type checkers use these for static analysis.
**Are there performance costs to using *args and kwargs?
Yes, but usually small. Each call creates a new tuple for *args and a new dict for **kwargs. In hot paths or very high call volume, prefer explicit parameters if the argument set is fixed.
**How are *args and kwargs used in class inheritance?
Subclasses often use *args and **kwargs in __init__ (or other methods) and pass them to super().__init__(*args, **kwargs) so the parent can receive the same arguments without the subclass declaring every parent parameter. The subclass can add its own parameters before or after the forwarding.
*args and **kwargs let you define functions that accept a variable number of positional and keyword arguments and let you unpack sequences and mappings when calling functions. Use them when you need flexible signatures, decorators, or forwarding (e.g., to super() or framework APIs). Keep argument order correct, and prefer explicit parameters when the API is fixed and performance matters. For more on building functions, see how to define functions in Python 3 and how to use variables in Python 3.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
Python is a flexible and versatile programming language that can be leveraged for many use cases, with strengths in scripting, automation, data analysis, machine learning, and back-end development. It is a great tool for both new learners and experienced developers alike.
Browse Series: 36 tutorials
Community and Developer Education expert. Former Senior Manager, Community at DigitalOcean. Focused on topics including Ubuntu 22.04, Ubuntu 20.04, Python, Django, and more.
Building future-ready infrastructure with Linux, Cloud, and DevOps. Full Stack Developer & System Administrator. Technical Writer @ DigitalOcean | GitHub Contributor | Passionate about Docker, PostgreSQL, and Open Source | Exploring NLP & AI-TensorFlow | Nailed over 50+ deployments across production environments.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
I appreciated this overview. Thank you.
What is important to note is that a dictionary called **kwargs is created
The dictionary is actually just called kwargs, isn’t it?
Thank you so much. All Digitalocean tutorials are very clear and easy to understand.
This is very simple to recap and also for new starters. Lisa deserves hike for this article :-).
This is fascinating. Like the other commenters said, simple, brief, and explanatory. My question is what would a function invocation look like which uses each of the four argument types? You showed the function signature though I’d like to see the invocation.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Full documentation for every DigitalOcean product.
The Wave has everything you need to know about building a business, from raising funding to marketing your product.
Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.
New accounts only. By submitting your email you agree to our Privacy Policy
Scale up as you grow — whether you're running one virtual machine or ten thousand.
Sign up and get $200 in credit for your first 60 days with DigitalOcean.*
*This promotional offer applies to new accounts only.