Sometimes you don't know in advance how many arguments a function needs to accept. Python solves this with two special parameter forms: *args, which collects any extra positional arguments into a tuple, and **kwargs, which collects any extra keyword arguments into a dictionary. The names args and kwargs are just convention — the single and double asterisks are what actually matter.

*args: Variable Positional Arguments

def total(*args):
    print(f"args is a {type(args).__name__}: {args}")
    return sum(args)

print(total(1, 2, 3))
print(total(10, 20, 30, 40))

Output:

args is a tuple: (1, 2, 3)
6
args is a tuple: (10, 20, 30, 40)
100

*args lets total() accept any number of positional arguments without you having to define a, b, c, d, ... ahead of time.

**kwargs: Variable Keyword Arguments

def describe(**kwargs):
    print(f"kwargs is a {type(kwargs).__name__}: {kwargs}")
    for key, value in kwargs.items():
        print(f"{key}: {value}")

describe(name="Asha", age=21, course="Python")

Output:

kwargs is a dict: {'name': 'Asha', 'age': 21, 'course': 'Python'}
name: Asha
age: 21
course: Python

Combining Regular Parameters, *args, and **kwargs

All three can appear in the same function, but they must follow a strict order: regular parameters first, then *args, then **kwargs.

def build_profile(username, *args, **kwargs):
    print(f"User: {username}")
    print(f"Extra positional: {args}")
    print(f"Extra keyword: {kwargs}")

build_profile("asha21", "admin", "verified", city="Mumbai", active=True)

Output:

User: asha21
Extra positional: ('admin', 'verified')
Extra keyword: {'city': 'Mumbai', 'active': True}

This pattern shows up constantly in real libraries — wrapper functions and decorators use *args, **kwargs so they can forward whatever arguments were passed to them, no matter the signature of the function underneath. For a complete worked example that puts this to use end to end, see our *args and **kwargs program.