*args lets a function accept any number of extra positional arguments, collected into a tuple, while **kwargs collects any number of extra keyword arguments into a dictionary.
Both let you write functions that accept a flexible, unknown number of arguments — the names "args" and "kwargs" are just convention; the important part is the * and ** symbols.
def add_all(*args):
return sum(args)
add_all(1, 2, 3) # args = (1, 2, 3), returns 6
add_all(1, 2, 3, 4) # args = (1, 2, 3, 4), returns 10
def describe(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
describe(name="Alex", age=30)
# name: Alex
# age: 30
You can use both in the same function definition (def func(*args, **kwargs)) to accept any combination of positional and keyword arguments — commonly seen in wrapper functions and decorators that need to pass arguments through to another function unchanged.
Last reviewed: September 2026