Question

Python *args and **kwargs Explained in Function Parameters

pythonsyntaxparameter-passingvariadic-functionsargument-unpacking

Question

In Python, what do *args and **kwargs mean when they appear in function definitions like these?

def foo(x, y, *args):
    pass

def bar(x, y, **kwargs):
    pass

I want to understand what the single asterisk (*) and double asterisk (**) do for parameters in a function definition, and how they affect the values a function can accept.

Short Answer

By the end of this page, you will understand how *args collects extra positional arguments, how **kwargs collects extra keyword arguments, when to use each one, and how they fit into real Python function design.

Concept

In Python function definitions, *args and **kwargs let a function accept a flexible number of arguments.

  • *args collects extra positional arguments into a tuple.
  • **kwargs collects extra keyword arguments into a dictionary.

This is useful when you do not know in advance exactly how many arguments will be passed to a function, or when you want to forward arguments to another function.

For example:

def foo(x, y, *args):
    print(x)
    print(y)
    print(args)

foo(1, 2, 3, 4, 5)

Output:

1
2
(3, 4, 5)

Here:

  • x gets 1
  • y gets 2
  • args gets the remaining positional values as a tuple: (3, 4, 5)

Now with **kwargs:

def bar(x, y, **kwargs):
    print(x)
    print(y)
    print(kwargs)

bar(1, 2, color="blue", size="large")

Output:

1
2
{'color': 'blue', 'size': 'large'}

Here:

  • x gets 1
  • y gets 2
  • kwargs gets all extra named arguments as a dictionary

The names args and kwargs are conventional, but not required. These are valid too:

def example(*numbers, **options):
    print(numbers)
    print(options)

What matters is the * and **, not the variable names.

This concept matters because many real Python APIs use flexible function signatures. You will see it in libraries, decorators, wrappers, logging helpers, configuration functions, and framework code.

Mental Model

Think of a function like a bag-check desk at an airport.

  • Regular parameters like x and y are for bags with fixed labels.
  • *args is a large bin for any extra unlabeled bags.
  • **kwargs is a separate bin for extra bags with labels attached.

So if a function knows it always needs two required values, but might receive more:

  • the extra unnamed values go into *args
  • the extra named values go into **kwargs

Another way to think about it:

  • *args = "collect leftover positional items"
  • **kwargs = "collect leftover named items"

This makes functions more flexible without forcing you to define every possible parameter up front.

Take Quiz

Syntax and Examples

Basic syntax

def func(required1, required2, *args, **kwargs):
    print(required1)
    print(required2)
    print(args)
    print(kwargs)

Example with *args

def add_numbers(*args):
    return sum(args)

print(add_numbers(1, 2, 3))
print(add_numbers(10, 20, 30, 40))

Output:

6
100

Explanation:

  • All positional arguments are collected into the tuple args
  • sum(args) works because args behaves like a normal tuple of numbers

Example with **kwargs

def print_user(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_user(name="Ava", role="admin", active=True)

Possible output:

name: Ava
role: admin
active: True

Explanation:

  • Every named argument is collected into the dictionary kwargs
  • You can loop through it like any other dictionary

Example using both

def demo(x, *args, **kwargs):
    print("x =", x)
    print("args =", args)
    print("kwargs =", kwargs)

demo(5, 10, 15, color="red", enabled=True)

Output:

x = 5
args = (10, 15)
kwargs = {'color': 'red', 'enabled': True}

Important note

*args and **kwargs are common names, but you could write:

def demo(*values, **options):
    print(values)
    print(options)

This works exactly the same way.

Step by Step Execution

Consider this code:

def show(a, b, *args, **kwargs):
    print("a:", a)
    print("b:", b)
    print("args:", args)
    print("kwargs:", kwargs)

show(1, 2, 3, 4, color="blue", size="M")

Step 1: Match required positional parameters

Python assigns the first positional argument to a:

  • a = 1

Then the second positional argument to b:

  • b = 2

Step 2: Collect remaining positional arguments into args

The remaining positional arguments are 3 and 4.

So:

  • args = (3, 4)

This is a tuple.

Step 3: Collect named arguments into kwargs

The keyword arguments are:

  • color="blue"
  • size="M"

So:

  • kwargs = {'color': 'blue', 'size': 'M'}

This is a dictionary.

Step 4: Print the values

The function prints:

a: 1
b: 2
args: (3, 4)
kwargs: {'color': 'blue', 'size': 'M'}

Key takeaway

Python fills normal parameters first, then gathers extras:

  1. required and named parameters
  2. extra positional arguments into *args
  3. extra keyword arguments into **kwargs

Real World Use Cases

1. Wrapper functions

You may write a function that adds logging before calling another function.

def logged_call(func, *args, **kwargs):
    print(f"Calling {func.__name__}")
    return func(*args, **kwargs)

This lets the wrapper accept any arguments the wrapped function needs.

2. Framework and library APIs

Many frameworks accept optional settings through keyword arguments.

def connect(host, port, **options):
    print(host, port, options)

This allows new options to be added later without changing every call.

3. Building utility functions

A function might need to accept any number of values.

def average(*numbers):
    return sum(numbers) / len(numbers)

4. Passing configuration data

Keyword arguments are useful for configuration-like input.

def create_user(username, **profile):
    return {
        "username": username,
        "profile": profile
    }

5. Decorators

Decorators often need to support functions with different signatures.

def debug(func):
    def wrapper(*args, **kwargs):
        print("args:", args)
        print("kwargs:", kwargs)
        return func(*args, **kwargs)
    return wrapper
Take Quiz

Real Codebase Usage

In real projects, developers use *args and **kwargs carefully, not everywhere.

Common patterns

Forwarding arguments

A function may pass received arguments to another function.

def save_with_log(*args, **kwargs):
    print("Saving data...")
    return save(*args, **kwargs)

This is common in wrappers, adapters, and decorators.

Optional extension points

Libraries sometimes use **kwargs to allow extra options without breaking older code.

def send_email(to, subject, **options):
    timeout = options.get("timeout", 30)
    retry = options.get("retry", False)

Validation

Good code often validates values inside kwargs.

def create_product(name, **kwargs):
    allowed = {"price", "stock", "category"}
    unknown = set(kwargs) - allowed
    if unknown:
        raise TypeError(f"Unexpected options: {unknown}")

Guard clauses

Functions may check for missing or invalid flexible inputs early.

def process_items(*args):
    if not args:
        raise ValueError("At least one item is required")
    return [str(item).upper() for item in args]

Compatibility layers

When refactoring a large codebase, **kwargs can help temporarily support old and new call styles while the code is being migrated.

Best practice in real code

  • Use flexible parameters when they truly add value
  • Prefer explicit parameter names when possible
  • Document what extra values are expected
  • Validate supported keys if using **kwargs
  • Avoid hiding too much behavior behind vague argument collection

Common Mistakes

1. Thinking args and kwargs are special names

They are just conventional names.

def test(*values, **options):
    print(values)
    print(options)

The special part is the * and **.

2. Forgetting the data types they create

  • *args becomes a tuple
  • **kwargs becomes a dictionary

Broken expectation:

def bad(*args):
    args.append(5)  # tuples do not have append

Fix:

def good(*args):
    values = list(args)
    values.append(5)
    return values

3. Using **kwargs without checking valid keys

Broken example:

def create_user(**kwargs):
    return kwargs["name"]

This crashes if name is missing.

Safer version:

def create_user(**kwargs):
    if "name" not in kwargs:
        raise ValueError("name is required")
    return kwargs["name"]

4. Overusing flexible arguments

This can make functions hard to understand.

Less clear:

def configure(*args, **kwargs):
    pass

Often better:

def configure(host, port, timeout=30, debug=False):
    pass

Use *args and **kwargs when flexibility is actually needed.

5. Mixing up positional and keyword arguments

def example(*args, **kwargs):
    print(args)
    print(kwargs)

example(1, 2, name="Sam")

Result:

  • args is (1, 2)
  • kwargs is {'name': 'Sam'}

6. Assuming **kwargs accepts any key format in code syntax

In a normal function call, keyword argument names must be valid identifiers.

This is valid:

print_user(name="Ava")

But this is not valid syntax:

# print_user(first-name="Ava")

If needed, dictionary unpacking at call time can be used in other contexts, but inside the function kwargs is still just a dictionary.

Comparisons

ConceptWhat it collectsResulting typeExample
Regular parameterOne specific argumentAny valuedef f(x):
*argsExtra positional argumentstupledef f(*args):
**kwargsExtra keyword argumentsdictdef f(**kwargs):

*args vs **kwargs

Feature*args**kwargs
AcceptsPositional argumentsKeyword arguments
Stored asTupleDictionary
Example callf(1, 2, 3)f(name="Ava", age=20)
Good forVariable-length lists of valuesOptional named settings

Explicit parameters vs flexible parameters

StyleExampleBest when
Explicitdef connect(host, port):Inputs are known and fixed
Flexible positionaldef add(*numbers):Number of items varies
Flexible keyworddef build(**options):Optional named settings vary
Mixeddef run(task, *args, **kwargs):A wrapper or forwarding function is needed

Related syntax in calls

In definitions:

  • *args means collect extra positional arguments
  • **kwargs means collect extra keyword arguments

In function calls, * and ** do the opposite job:

  • *sequence unpacks positional values
  • **mapping unpacks keyword values

That is a related but separate concept.

Take Quiz

Cheat Sheet

# Collect extra positional arguments
def func(*args):
    print(args)   # tuple

# Collect extra keyword arguments
def func(**kwargs):
    print(kwargs) # dict

# Use both
def func(a, b, *args, **kwargs):
    pass

Rules

  • *args collects leftover positional arguments
  • **kwargs collects leftover keyword arguments
  • args and kwargs are naming conventions, not keywords
  • *args is a tuple
  • **kwargs is a dictionary
  • Use them when the number or names of inputs may vary

Example

def demo(x, *args, **kwargs):
    print(x)
    print(args)
    print(kwargs)

demo(1, 2, 3, color="red")

Result:

1
(2, 3)
{'color': 'red'}

Good uses

  • Wrappers
  • Decorators
  • Forwarding arguments
  • Variable-length input
  • Optional configuration

Watch out for

  • Making function APIs too vague
  • Forgetting args is a tuple
  • Forgetting kwargs is a dict
  • Not validating supported keyword options

FAQ

What does *args mean in Python?

*args means a function can accept any number of extra positional arguments. Inside the function, they are stored as a tuple.

What does **kwargs mean in Python?

**kwargs means a function can accept any number of extra keyword arguments. Inside the function, they are stored as a dictionary.

Are args and kwargs special Python keywords?

No. They are just common names. The special meaning comes from * and ** in the function definition.

Can I use both *args and **kwargs in the same function?

Yes. This is very common in wrappers and decorators.

When should I use *args?

Use *args when a function should accept a variable number of positional values, such as many numbers to sum.

When should I use **kwargs?

Use **kwargs when a function should accept optional named settings or when forwarding options to another function.

Is *args always a tuple and **kwargs always a dictionary?

Yes. In a function definition, *args becomes a tuple and **kwargs becomes a dictionary.

Should I always use *args and **kwargs for flexibility?

No. Prefer explicit parameters when the expected inputs are known. Use flexible arguments only when they make the function genuinely more useful.

Take Quiz

Mini Project

Description

Build a small Python utility function that records events with a required event name, any number of extra positional tags, and any number of keyword details. This demonstrates how *args and **kwargs can be used together in a realistic logging-style function.

Goal

Create a function that accepts fixed parameters plus flexible extra data, then prints the collected information in a clear format.

Requirements

  • Create a function named log_event that requires one parameter called event_name
  • Allow the function to accept any number of extra positional arguments as tags
  • Allow the function to accept any number of keyword arguments as details
  • Print the event name, the tags, and the details in a readable way
  • Call the function with at least two different examples
Take Quiz

Keep learning

Related questions

View more