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.


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.

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}

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.

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.


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.

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.

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="Podgorica"))
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.


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.

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}

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.