PYTHON

Python *args and **kwargs: Syntax, Usage, and Examples

In Python, *args and **kwargs let a function accept a flexible number of arguments. You use them when you don’t know ahead of time how many values someone will pass into your function.

You’ll usually reach for them when you want a function to stay flexible as requirements change, without rewriting the signature every time.


How to Use *args and **kwargs in Python

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

The double asterisk in **kwargs signals “keyword arguments,” while a single * in *args signals “positional arguments.”

Basic syntax

defprint_args(*args):
print(args)

print_args(1,2,3)# (1, 2, 3)

defprint_kwargs(**kwargs):
print(kwargs)

print_kwargs(name="Sam", role="admin")# {'name': 'Sam', 'role': 'admin'}

Using both in the same function

You can accept both extra positional and extra keyword arguments in one function.

defshow_values(*args, **kwargs):
print("Args:", args)
print("Kwargs:", kwargs)

show_values("apple","banana", color="red", price=2)

Output:

  • args becomes a tuple: ("apple", "banana")
  • kwargs becomes a dictionary: {"color": "red", "price": 2}

In a call like that, the positional values are the passed arguments, and each keyword entry has a parameter name mapped to a value.

Order rules for parameters

When you define a function, *args must come before **kwargs.

✅ Valid:

defexample(a, *args, **kwargs):
print(a, args, kwargs)

❌ Invalid:

defexample(a, **kwargs, *args):
pass

Also, in function calls, positional arguments must come before keyword arguments.

✅ Valid:

example(1,2,3, x=10)

❌ Invalid:

example(a=1,2,3)

You don’t have to name them args and kwargs

The * and ** are what matter. The names are just a convention.

defdemo(*values, **options):
print(values)
print(options)

demo(10,20, debug=True)

Still, most Python developers stick with *args and **kwargs because everyone recognizes them instantly.

In other words, args and kwargs are just a variable name choice, while the * and ** decide how Python collects the inputs.

Unpacking with * and **

The same symbols are also used to unpack values when calling functions.

defadd(a, b, c):
return a + b + c

nums = [1,2,3]
print(add(*nums))# 6

defgreet(name, punctuation="!"):
returnf"Hi {name}{punctuation}"

options = {"name":"Aisha","punctuation":"?"}
print(greet(**options))# Hi Aisha?

Unpacking is the “reverse” of collecting. Instead of bundling arguments into a tuple or dict, you spread them out into separate arguments.

If a key doesn’t match a parameter, you’ll get a typeerror, because Python doesn’t know where to put that named value.


When to Use *args and **kwargs

You don’t need *args and **kwargs for every function. Use them when flexibility actually helps.

1) When the number of inputs isn’t fixed

Some functions naturally take “as many as you want.”

For example, adding up a list of prices:

deftotal_cost(*args):
returnsum(args)

print(total_cost(3,5,10))# 18

No need to force the caller to pack values into a list first.

This is a good use case when the number of positional arguments can change depending on the data you have.

2) When you’re building a wrapper function

Wrapper functions often take extra arguments and pass them along to another function. This is common in logging, timing, caching, and decorators.

deflog_call(func, *args, **kwargs):
print("Calling:", func.__name__)
return func(*args, **kwargs)

That way, the wrapper works with almost any function signature.

3) When you want optional configuration with keyword arguments

Keyword arguments are great for settings like debug=True, timeout=5, or retries=3.

defconnect_to_service(**kwargs):
    timeout = kwargs.get("timeout",10)
    retries = kwargs.get("retries",1)
returnf"timeout={timeout}, retries={retries}"

print(connect_to_service(timeout=5, retries=3))

This keeps function calls readable and gives you space to add more options later.

In this pattern, kwargs.get("timeout", 10) lets you set a default value when the caller doesn’t provide that option.

4) When you’re working with libraries that accept them

Many frameworks and libraries expect functions that accept *args and **kwargs, especially in places like:

  • custom decorators
  • event handlers
  • class inheritance with super()
  • Django or Flask utilities

If a library calls your function and passes extra stuff, accepting **kwargs can stop your code from breaking.


Examples of *args and **kwargs in Python

Here are a few practical examples you’ll actually see in real projects.

Example 1: Building a message from multiple parts with *args

defbuild_message(*args, separator=" "):
return separator.join(args)

print(build_message("Hello","world"))# Hello world
print(build_message("Save","your","work","!"))# Save your work !
print(build_message("a","b","c", separator="-"))# a-b-c

Here, *args collects the words, then separator controls how they’re joined.

Notice how separator is a normal keyword argument, not part of args.


Example 2: A function that accepts any number of scores

defaverage_score(*args):
ifnot args:
return0
returnsum(args) /len(args)

print(average_score(90,80,100))# 90.0
print(average_score())# 0

In this example, args is a tuple, so you can safely call len(args) or loop through it.


Example 3: Using **kwargs for feature flags

Let’s say you want a function that formats a profile, with some optional extras.

defformat_profile(name, **kwargs):
    profile =f"Name: {name}"
if kwargs.get("show_email"):
        profile +=f"\nEmail: {kwargs.get('email','N/A')}"
if kwargs.get("show_city"):
        profile +=f"\nCity: {kwargs.get('city','Unknown')}"
return profile

print(format_profile("Leila"))
print(format_profile("Leila", show_city=True, city="Boston"))
print(format_profile("Leila", show_email=True, email="leila@example.com"))

kwargs becomes a dictionary, so you can check values using get() without crashing if a key doesn’t exist.

These flags are often called named arguments, because you pass them by name instead of by position.


Example 4: Forwarding arguments to another function

This is one of the most common real-life reasons to use both.

defnotify_user(message, **kwargs):
    channel = kwargs.get("channel","email")
    urgent = kwargs.get("urgent",False)
returnf"{channel.upper()} | urgent={urgent} |{message}"

defsend_notification(*args, **kwargs):
# This wrapper accepts anything and forwards it
return notify_user(*args, **kwargs)

print(send_notification("Your order shipped"))
print(send_notification("Payment failed", channel="sms", urgent=True))

The wrapper doesn’t care how many arguments the target function needs, it just forwards them.


Example 5: Unpacking a list and dict into a function call

Unpacking shows up everywhere, especially when you already have values stored in a list or dictionary.

defadd(a, b, c):
return a + b + c

numbers = (4,5,6)
print(add(*numbers))# 15

defcreate_ticket(title, priority="low", assignee=None):
return {"title": title,"priority": priority,"assignee": assignee}

data = {"title":"Fix checkout bug","priority":"high","assignee":"Niko"}
print(create_ticket(**data))

  • unpacks sequences, * unpacks dictionaries.

Learn More About *args and **kwargs

Once you’ve used them a few times, you’ll start spotting patterns and also a few traps.

args is a tuple, **kwargs is a dictionary

That matters because you can do normal tuple and dictionary operations.

definspect(*args, **kwargs):
print("First arg:", args[0]if argselseNone)
print("Kwarg keys:",list(kwargs.keys()))

inspect("x","y", mode="fast", retries=2)

args works like an ordered list of values, and kwargs works like a label-based settings object.

Keyword-only arguments with * in function definitions

Python also uses * to force keyword-only parameters, even without *args.

defcreate_user(username, *, is_admin=False):
return {"username": username,"is_admin": is_admin}

print(create_user("mika"))
print(create_user("mika", is_admin=True))

This prevents confusing calls like create_user("mika", True). You can only pass is_admin by name.

Don’t blindly accept everything, use it intentionally

It can be tempting to throw *args, **kwargs into every function “just in case.”

That usually backfires because:

  • the function signature becomes unclear
  • mistakes slip through quietly
  • debugging gets harder

A good rule: if you know the parameters, list them. Use flexible arguments when you truly need flexibility.

A nice habit is to check the official docs for a library function before you decide to mirror its signature with *args and **kwargs.

Passing kwargs through multiple layers

You’ll often see a pattern where one function takes **kwargs and passes them further down.

defget_data(source, **kwargs):
return fetch_from_source(source, **kwargs)

deffetch_from_source(source, timeout=10, retries=1):
returnf"source={source}, timeout={timeout}, retries={retries}"

print(get_data("api", timeout=5))

This makes it easy to offer options without rewriting the wrapper each time.

Common beginner mistake: mixing up args and kwargs

  • args collects values without names:
deff(*args):
print(args)

f(1,2,3)

  • *kwargs collects values with names:
defg(**kwargs):
print(kwargs)

g(a=1, b=2)

Trying to pass keyword arguments into a function that only accepts *args will raise an error.

Another common mistake: expecting **kwargs to include required keyword parameters

Required parameters don’t automatically go into kwargs. They match by name first.

defexample(name, **kwargs):
print(name)
print(kwargs)

example("Kai", age=30)

Output:

  • name is "Kai"
  • kwargs is {"age": 30}

In many classes, you’ll see __init__ (sometimes shortened to init) use keyword options so constructors stay readable as they grow.

If you’re copying a snippet from a tutorial, for example, it helps to run it once and then tweak one argument at a time, so you can see exactly what changes.


Summary

args and *kwargs let Python functions accept a variable number of arguments, which makes them useful for wrappers, helpers, and configurable APIs.

args collects extra positional values into a tuple, *kwargs collects extra named values into a dictionary, and both can also be used to unpack data when calling functions.