`*args` and `**kwargs` — Flexible Function Arguments

Published:
Last updated:
ByJeferson Peter
1 min read
Python
Share this post:

As Python codebases grow, functions often need to handle varying inputs.
That’s where *args and **kwargs come in — they allow functions to accept an arbitrary number of arguments without constantly changing the function signature.

When used with intention, they make APIs more flexible and future-proof.


Understanding *args

*args collects extra positional arguments into a tuple.

def log_values(*args):
    for value in args:
        print(value)

log_values(1, 2, 3)

This is useful when:

  • the number of inputs isn’t fixed
  • order matters
  • you want a simple, lightweight interface

Understanding **kwargs

**kwargs collects extra keyword arguments into a dictionary.

def log_config(**kwargs):
    for key, value in kwargs.items():
        print(key, value)

log_config(debug=True, retries=3)

This pattern is common in:

  • configuration objects
  • wrappers and decorators
  • extensible APIs

Using *args and **kwargs together

def handler(*args, **kwargs):
    print("args:", args)
    print("kwargs:", kwargs)

handler(1, 2, debug=True)

This is often seen in libraries that need maximum flexibility.


Forwarding arguments

One of the most practical uses is argument forwarding:

def wrapper(*args, **kwargs):
    return target_function(*args, **kwargs)

This allows you to wrap or extend behavior without changing the original interface.


A common mistake to avoid

Avoid using *args and **kwargs when the function expects a clear, fixed contract.

# Hard to understand API
def process(*args, **kwargs):
    ...

If the inputs are known and stable, explicit parameters are more readable and safer.


Conclusion

In my experience, *args and **kwargs shine when flexibility is truly needed.

Used sparingly, they help build clean and extensible APIs.
Overused, they can hide intent and make code harder to reason about.

Share this post: