*args and **kwargs Explained

· 7 min read · Updated March 7, 2026 · beginner
args kwargs functions parameters beginner

When you write functions in Python, you often know exactly how many parameters you need. But sometimes you want to create a function that can accept any number of arguments — whether that’s one, ten, or none at all. This is where *args and **kwargs come in. They let you build flexible functions that handle varying amounts of input data.

In this tutorial, you will learn what *args and **kwargs do, how to use them in your own functions, and when they are most useful in real-world code.

Understanding Positional Arguments

Before diving into *args, let’s quickly review how regular function parameters work. When you define a function with named parameters, each argument you pass must be provided in the correct order:

def greet(name, greeting):
    print(f"{greeting}, {name}!")

greet("Alice", "Hello")  # Output: Hello, Alice!
greet("Bob", "Hi")       # Output: Hi, Bob!

This works well when you know exactly what you need. But what if you want to create a function that can greet any number of people?

Using *args for Variable Positional Arguments

The asterisk (*) before args tells Python to collect any additional positional arguments into a tuple. This lets your function accept zero or more arguments:

def greet_all(*names):
    for name in names:
        print(f"Hello, {name}!")

greet_all("Alice", "Bob", "Charlie")
# Output:
# Hello, Alice!
# Hello, Bob!
# Hello, Charlie!

greet_all("Diana")
# Output:
# Hello, Diana!

greet_all()  # No output — the tuple is empty

Inside the function, names becomes a tuple containing all the positional arguments passed in. You can iterate over it, check its length, or access individual elements just like any other tuple.

The name args is just a convention — you could use *numbers or *items if that makes more sense for your function. The important part is the asterisk.

A Practical Example

Here’s a function that calculates the sum of any number of values:

def sum_all(*numbers):
    total = 0
    for num in numbers:
        total += num
    return total

print(sum_all(1, 2, 3))        # Output: 6
print(sum_all(10, 20, 30, 40))  # Output: 100
print(sum_all())                # Output: 0

This is cleaner than writing separate functions for different argument counts, or forcing callers to pass a list.

Understanding Keyword Arguments

Now let’s look at keyword arguments. When you pass arguments using the key=value syntax, those become keyword arguments:

def create_user(username, email, age):
    return {"username": username, "email": email, "age": age}

user = create_user(username="alice", email="alice@example.com", age=30)

The function above requires exactly three keyword arguments. But what if you want to allow any number of extra properties?

Using **kwargs for Variable Keyword Arguments

The double asterisk (**) before kwargs collects any additional keyword arguments into a dictionary. Each key becomes a string, and each value can be anything:

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

print_info(name="Alice", age=30, city="London")
# Output:
# name: Alice
# age: 30
# city: London

Just like args, the name kwargs is a convention — you could use **options or **config. The double asterisk is what matters.

A Practical Example

Here’s a function that builds a user profile with required and optional fields:

def create_profile(username, **extra_fields):
    profile = {"username": username}
    profile.update(extra_fields)
    return profile

profile1 = create_profile("alice", email="alice@example.com", bio="Developer")
print(profile1)
# Output: {'username': 'alice', 'email': 'alice@example.com', 'bio': 'Developer'}

profile2 = create_profile("bob", age=25, country="USA", hobby="Running")
print(profile2)
# Output: {'username': 'bob', 'age': 25, 'country': 'USA', 'hobby': 'Running'}

This pattern is common in many Python libraries. It lets users pass only the extra information they need without you having to define every possible parameter.

Combining *args and **kwargs

You can use both *args and **kwargs in the same function. By convention, *args comes first, then **kwargs:

def flexible_function(*args, **kwargs):
    print("Positional arguments:", args)
    print("Keyword arguments:", kwargs)

flexible_function(1, 2, 3, name="Alice", age=30)
# Output:
# Positional arguments: (1, 2, 3)
# Keyword arguments: {'name': 'Alice', 'age': 30}

This combination is particularly useful when you’re building functions that act as wrappers or when working with decorators.

Unpacking Arguments

The inverse of collecting arguments is unpacking them. You can use * to unpack a list or tuple into positional arguments, and ** to unpack a dictionary into keyword arguments:

def introduce(name, age, city):
    print(f"I am {name}, {age} years old, from {city}.")

# Unpacking a list
details = ["Alice", 30, "London"]
introduce(*details)
# Output: I am Alice, 30 years old, from London.

# Unpacking a dictionary
info = {"name": "Bob", "age": 25, "city": "Paris"}
introduce(**info)
# Output: I am Bob, 25 years old, from Paris.

This is useful when you have data stored in a collection and want to pass it to a function that expects individual arguments.

Common Mistakes to Avoid

There are a few pitfalls to watch out for when using *args and **kwargs:

Putting **kwargs before *args

This will cause a syntax error:

# Wrong
def bad_function(**kwargs, *args):
    pass

# Correct order
def good_function(*args, **kwargs):
    pass

Mixing too many argument types

Arguments must be provided in a specific order: regular parameters first, then *args, then **kwargs. Python enforces this to avoid ambiguity:

def correct_order(a, b, *args, **kwargs):
    print(f"Regular: {a}, {b}")
    print(f"Args: {args}")
    print(f"Kwargs: {kwargs}")

correct_order(1, 2, 3, 4, 5, name="Alice", age=30)
# Output:
# Regular: 1, 2
# Args: (3, 4, 5)
# Kwargs: {'name': 'Alice', 'age': 30}

Using mutable defaults with *args

A common mistake is confusing *args with mutable default arguments. Remember that each call to the function gets a new empty tuple for *args, so you do not have the same problem as with mutable default values:

# This is fine — args is a new tuple each call
def append_to(*args):
    args = list(args)  # Convert to list if you need to modify
    args.append("new item")
    return args

print(append_to(1, 2, 3))  # Output: [1, 2, 3, 'new item']
print(append_to(4, 5))     # Output: [4, 5, 'new item'] — not [1, 2, 3, 'new item']!

This is different from the mutable default argument gotcha, where using a list as a default accumulates data across calls.

Working with Built-in Functions

Many Python built-in functions use these patterns internally. For example, the print() function accepts any number of objects to print:

print("hello")           # 1 argument
print("hello", "world")  # 2 arguments
print(1, 2, 3, 4, 5)    # 5 arguments

The print function is defined roughly like this internally — it accepts *objects to handle any number of arguments. Similarly, many functions in popular libraries use **kwargs to allow optional configuration without requiring you to pass every possible option.

Real-World Use Cases

Where do you actually see *args and **kwargs in practice? Here are some common scenarios:

Wrapper functions

When you build a function that calls another function, you often want to pass through whatever arguments were given:

def log_call(func, *args, **kwargs):
    print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
    result = func(*args, **kwargs)
    print(f"Result: {result}")
    return result

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

log_call(add, 3, 5)
# Output:
# Calling add with args=(3, 5), kwargs={}
# Result: 8

Class methods

Classes often use *args and **kwargs when defining methods that should be flexible, especially with inheritance:

class Database:
    def connect(self, *args, **kwargs):
        # Connection logic here
        print(f"Connecting with args={args}, kwargs={kwargs}")

db = Database()
db.connect(host="localhost", port=5432)

Decorators

Decorators are a common pattern that relies on *args and **kwargs to wrap any function regardless of its signature:

def timer(func):
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.4f} seconds")
        return result
    return wrapper

@timer
def slow_function():
    import time
    time.sleep(1)

slow_function()  # Outputs the time taken

Next Steps

You now understand how to write flexible functions that accept any number of arguments. This is a skill you’ll use frequently as you build more complex Python programs.

The next tutorial in the Python Fundamentals series covers String Formatting with f-strings. You’ll learn how to create dynamic, readable strings in Python — a skill that pairs well with what you learned here, since f-strings work great with data from *args and **kwargs.

For practice, try modifying one of your existing functions to accept variable arguments. See how it changes the way you can call that function.