Report this

What is the reason for this report?

How To Use *args and **kwargs in Python 3

Updated on March 6, 2026
How To Use *args and **kwargs in Python 3

Introduction

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.

Key Takeaways

  • *args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict.
  • Legal argument order in a function signature is: standard positional parameters, then *args, then keyword-only parameters, then **kwargs. Reversing or mixing this order causes a SyntaxError.
  • The single asterisk (*) and double asterisk (**) are the operators that matter; the names args and kwargs are conventions only.
  • You can use * and ** at the call site to unpack a sequence or mapping into positional and keyword arguments when calling a function.
  • Type annotations for *args and **kwargs (e.g., *args: int, **kwargs: str) apply to each element or value, not to the tuple or dict as a whole.
  • Prefer explicit parameters when the set of arguments is fixed and small; use *args/**kwargs for flexible APIs, decorators, and forwarding, and be aware of the small overhead of tuple and dict creation in hot paths.

Prerequisites

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.

Understanding Python Function Arguments

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 vs. Variable Arguments

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

Argument Ordering Rules

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:

full_example.py
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")
  1. python3 full_example.py
Output
name=Sam, age=30 extra positional args: ('extra1', 'extra2') role=admin extra kwargs: {'team': 'infra', 'region': 'us-east'}

Using *args to Accept Variable Positional Arguments

*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.

How *args Works Internally

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:

args_type_check.py
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)
  1. 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.

*args in Practice

A function that multiplies all given numbers can be written with *args so it works for two, three, or more arguments:

lets_multiply.py
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)
  1. python3 lets_multiply.py
Output
20 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.

Using **kwargs to Accept Variable Keyword Arguments

**kwargs collects extra keyword arguments into a single dict. Each argument must be passed with a keyword (e.g., name="Sam").

How **kwargs Works Internally

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:

kwargs_type_check.py
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")
  1. 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.

**kwargs in Practice

A simple way to inspect what was passed is to print kwargs:

print_kwargs.py
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)
  1. 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:

print_values.py
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")
  1. python3 print_values.py
Output
The 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:

kwargs_access.py
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")
  1. python3 kwargs_access.py
Output
Hello, 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.

Combining *args and **kwargs in One Function

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:

log_message.py
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")
  1. 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.

Using *args and **kwargs When Calling Functions

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.

Unpacking a List with *

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:

unpack_call.py
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)
  1. python3 unpack_call.py
Output
Hello, Ada Lovelace from London.

Here *parts supplies first and last, and **location supplies city. You can mix literal arguments with unpacking:

some_args.py
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)
  1. python3 some_args.py
Output
arg_1: Sammy arg_2: Casey arg_3: Alex

Unpacking a Dictionary with **

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):

some_kwargs.py
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)
  1. python3 some_kwargs.py
Output
kwarg_1: Val kwarg_2: Harper kwarg_3: Remy

Common Unpacking Errors

Two errors appear frequently when unpacking.

Error 1: Too many positional arguments:

unpack_error_positional.py
def add(a, b):
    return a + b

values = [1, 2, 3]  # one element too many
add(*values)
  1. python3 unpack_error_positional.py
Output
TypeError: 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:

unpack_error_keyword.py
def greet(name, city):
    print(f"Hello, {name} from {city}.")

data = {"name": "Sam", "country": "India"}  # 'country' is not a parameter
greet(**data)
  1. python3 unpack_error_keyword.py
Output
TypeError: 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.

Practical Use Cases

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.

Decorator Pattern

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:

decorator_example.py
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)
  1. python3 decorator_example.py
Output
Calling 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.

Class Inheritance with super()

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:

inheritance_example.py
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()
  1. python3 inheritance_example.py
Output
Rex (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.

Framework-Style Flexible APIs

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:

flask_style.py
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"))
  1. python3 flask_style.py
Output
Routing '/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.

Type Annotations with *args and **kwargs

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:

type_annotations_example.py
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")
  1. python3 type_annotations_example.py
Output
Sum: 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:

  1. pip3 install mypy
  2. 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.

Performance Considerations

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:

perf_check.py
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")
  1. python3 perf_check.py
Output
explicit: 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.

Alternative Patterns

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.

FAQ

**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.

Conclusion

*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.

Learn more about our products

Tutorial Series: How To Code in Python

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.

About the author(s)

Lisa Tagliaferri
Lisa Tagliaferri
Author
See author profile

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.

Vinayak Baranwal
Vinayak Baranwal
Editor
Technical Writer II
See author profile

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.

Still looking for an answer?

Was this helpful?


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!

Brilliant refresher.

Thank you for your tutorial. Simple to understand and comprehensive.

As of 3.6 dicts stay in order. :)

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?

Thanks , helped me alot

Thanks

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.

Creative CommonsThis work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.
Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.