5 Ways You’re Misusing *args and **kwargs in Python

Master Python’s most misunderstood feature — avoid the silent bugs, confusing APIs, and lost readability that come with misusing *args and…

5 Ways You’re Misusing *args and **kwargs in Python
Photo by Chris Lawton on Unsplash

You’re probably using *args and **kwargs because you’ve seen others do it. But do you really know what they’re doing behind the scenes?

5 Ways You’re Misusing *args and **kwargs in Python

Master Python’s most misunderstood feature — avoid the silent bugs, confusing APIs, and lost readability that come with misusing *args and **kwargs.

Python gives us a lot of flexibility, and one of the most powerful tools in our arsenal is *args and **kwargs.

These special symbols allow us to write functions that accept a variable number of arguments — a dream for any developer building reusable components or dynamic interfaces.

But with great power comes great potential for misuse.

If you’ve been coding in Python for a while, chances are you’ve used *args and **kwargs. But are you using them the right way?

In this article, we’ll explore 5 common ways developers misuse *args and **kwargs, and how to turn those mistakes into clean, Pythonic code.


1. Using *args and **kwargs Without Documentation

Let’s face it — when you see a function like this:

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

…you have no clue what to pass in.

The Mistake:
Using *args and **kwargs without documenting or clearly specifying what the function expects is a recipe for confusion. It’s great for flexibility, terrible for readability.

The Fix:
If you use them, always document expected parameters in the function docstring or explicitly unpack the ones you need.

def send_email(*args, **kwargs): 
    """ 
    Sends an email. 
 
    Keyword arguments: 
    subject -- Subject of the email 
    to -- Recipient email address 
    body -- Body content of the email 
    """ 
    subject = kwargs.get('subject') 
    to = kwargs.get('to') 
    body = kwargs.get('body') 
    # ...send email

Better yet? Use keyword-only arguments.

def send_email(*, subject, to, body): 
    # No need for **kwargs if arguments are known

2. Overusing *args and **kwargs When Explicit Parameters Work Better

The Mistake:
Trying to be too clever by using *args and **kwargs everywhere, even when the function parameters are known and fixed.

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

Sure, it works. But it’s not always the best choice, especially when your function expects exactly two numbers.

The Fix:
Be explicit when you can. Use *args only when the number of inputs genuinely varies.

def add(a, b): 
    return a + b

Or, if you do want to support variable input:

def add(*args): 
    if not args: 
        raise ValueError("At least one number is required") 
    return sum(args)

3. Modifying *args and **kwargs Directly

The Mistake:
Trying to modify args or kwargs directly as if they were regular variables.

def modify(*args): 
    args[0] = 10  # ❌ This will throw a TypeError

Why? Because args is a tuple — it’s immutable.

The Fix:
If you need to modify it, convert it to a list first.

def modify(*args): 
    args = list(args) 
    args[0] = 10 
    return args

For kwargs, which is a dictionary, modification is allowed — but proceed with caution. Don't mutate it unless absolutely necessary.

4. Forgetting to Forward *args and **kwargs in Wrappers

If you’ve ever written a decorator or a wrapper function and it mysteriously broke, this might be why.

The Mistake:
Forgetting to pass *args and **kwargs to the wrapped function.

def log_decorator(func): 
    def wrapper(): 
        print("Calling function...") 
        return func()  # ❌ Breaks if func expects arguments 
    return wrapper

The Fix:
Always forward the arguments properly.

def log_decorator(func): 
    def wrapper(*args, **kwargs): 
        print("Calling function...") 
        return func(*args, **kwargs) 
    return wrapper

Even better: use functools.wraps to preserve metadata.

5. Confusing *args and **kwargs in Function Calls

The Mistake:
Using *args when the function expects keyword arguments — or vice versa.

def greet(name, age): 
    print(f"Hello, {name}. You are {age}.") 
 
args = ("Aashish", 30) 
kwargs = {"name": "Aashish", "age": 25} 
 
greet(args)      # ❌ TypeError 
greet(**args)    # ❌ TypeError again

The Fix:
Use * when unpacking positional arguments and ** when unpacking keyword arguments — don’t mix them.

greet(*args)     # ✅ Works 
greet(**kwargs)  # ✅ Works

Bonus Tip: Combine Explicit Parameters with *args/**kwargs

Don’t feel like it’s all or nothing.

You can combine required parameters, flexible arguments, and keyword-only arguments elegantly:

def process_request(user_id, *args, timeout=30, **kwargs): 
    print(user_id, args, timeout, kwargs)

This gives you a good mix of strictness and flexibility.


Final Thoughts

*args and **kwargs are beautiful features that make Python incredibly dynamic and flexible. But misuse them — and you’ll end up with code that’s confusing, buggy, or hard to maintain.

Use them wisely. Be intentional. Be explicit where it matters. And always prioritize clarity over cleverness.


Enjoyed this article?
Follow me for more Python tricks, debugging tips, and clean code practices!
Let’s write better Python together.

Photo by Matúš Gocman on Unsplash