Python *args and **kwargs look strange the first time you see them, but they solve a very practical problem: they let a function accept a flexible number of arguments. Instead of writing several versions of the same function for different inputs, you can design one function that works with many positional arguments, many named options, or both.
This English guide is adapted for developers who want a clear, practical explanation rather than a literal translation. You will learn what *args and **kwargs mean, why the asterisks matter more than the names, how Python collects values internally, how unpacking works, and when using these tools improves your code. If you are still learning the basics of functions, start with this guide to functions in Python first.
The Short Version
*args collects extra positional arguments into a tuple. **kwargs collects extra keyword arguments into a dictionary. The names args and kwargs are conventions, not magic words. Python only cares about the single asterisk and the double asterisk. You could technically write *numbers and **options, and the behavior would be the same.
In professional Python code, however, *args and **kwargs are widely recognized. Using the conventional names makes your code easier for other developers to read. The Python tutorial section on arbitrary argument lists explains the official syntax and why the order of parameters matters.
How *args Works
Use *args when a function should accept any number of positional arguments. Python packs those values into a tuple. That means you can loop over them, check their length, access them by index, or pass them to other functions that expect an iterable. If tuples are still new to you, this article on tuples in Python will help you understand why args is immutable.
def add_numbers(*args):
total = 0
for number in args:
total += number
return total
print(add_numbers(1, 2, 3))
print(add_numbers(10, 20, 30, 40))
print(add_numbers())The function works even when no extra arguments are passed. In that case, args is an empty tuple. This is useful when you want a function to support optional data without forcing callers to build a list manually. It also keeps your function signature compact when the number of inputs is naturally variable.
When *args Makes Sense
The best use case for *args is a function where each extra argument has the same meaning. For example, a function that calculates a total, finds the largest value, joins several strings, logs several message parts, or receives several file paths can be a good candidate. If the arguments have different meanings, explicit parameter names are usually better.
def build_sentence(*words):
return " ".join(words)
print(build_sentence("Python", "is", "flexible"))This function is readable because every positional argument is a word. If you had a function that expected a name, age, email, and city, *args would make the code less clear. In that situation, named parameters or a dictionary are usually easier to understand. Good Python code is not just flexible; it is explicit enough to maintain later.
How **kwargs Works
Use **kwargs when a function should accept any number of keyword arguments. Python packs those values into a dictionary. Each key is the argument name, and each value is the value passed by the caller. If dictionaries are still confusing, review this guide to Python dictionaries before using **kwargs heavily.
def show_profile(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
show_profile(name="Ana", role="Developer", country="Brazil")This pattern is useful for optional configuration. You may not know every setting a caller wants to provide, but you can still accept those settings, inspect them, apply defaults, or pass them forward to another function. Libraries, frameworks, decorators, and class constructors often use this pattern because it gives APIs room to grow without breaking old code.
Accessing Values Safely in kwargs
Because kwargs is a dictionary, you can access values using square brackets or .get(). Square brackets are useful when a key must exist. The .get() method is safer when a key is optional because it lets you provide a default value.
def create_user(**kwargs):
username = kwargs["username"]
is_admin = kwargs.get("is_admin", False)
language = kwargs.get("language", "en")
return {
"username": username,
"is_admin": is_admin,
"language": language,
}
print(create_user(username="maria", language="pt"))If username is missing, this function raises a clear error. If is_admin or language is missing, the function uses defaults. That combination is common in real projects: required values should be explicit, while optional settings can be collected through **kwargs.
Using Regular Parameters with *args and **kwargs
You can combine normal parameters, *args, and **kwargs in the same function. The order matters. Regular positional parameters come first, then *args, then keyword-only parameters if you have them, and finally **kwargs. Putting them in the wrong order causes a syntax error.
def send_notification(user, *messages, urgent=False, **metadata):
print(f"User: {user}")
print("Messages:", messages)
print("Urgent:", urgent)
print("Metadata:", metadata)
send_notification(
"ana",
"Server restarted",
"Cache cleared",
urgent=True,
source="admin_panel",
ip="192.168.1.10",
)This is a powerful signature because it separates the required user, a flexible list of message strings, a keyword-only flag, and extra metadata. You will see this kind of structure in decorators, CLI tools, web frameworks, and wrapper functions. If you want to go deeper into wrappers, this guide to Python decorators is a strong next step.
Unpacking with * and **
The asterisk syntax also works in the opposite direction. When defining a function, *args packs values into a tuple. When calling a function, *some_list unpacks a list or tuple into positional arguments. Similarly, **some_dict unpacks a dictionary into keyword arguments.
def calculate_total(a, b, c):
return a + b + c
values = [10, 20, 30]
print(calculate_total(*values))
profile = {"name": "Ana", "role": "Developer"}
def introduce(name, role):
print(f"{name} works as a {role}")
introduce(**profile)This is one of the most practical parts of the syntax. It lets you convert existing data structures into function calls. If your data already lives in a list, tuple, or dictionary, you do not need to manually write every argument. You can unpack it directly, as long as the number and names of values match the function signature. The Python reference on function calls describes how positional and keyword arguments are evaluated.
A Real Example: Flexible Logging
Logging is a good practical example because you may want to pass many message parts and optional context. The message parts can be positional arguments, while context such as user ID, request ID, or environment can be keyword arguments. This keeps the call readable without forcing every log entry into the same rigid structure.
def log_event(level, *message_parts, **context):
message = " ".join(str(part) for part in message_parts)
print(f"[{level}] {message}")
if context:
print("Context:")
for key, value in context.items():
print(f" {key}: {value}")
log_event(
"INFO",
"User",
"logged in",
user_id=42,
ip="192.168.1.20",
)This pattern appears in many libraries because it creates a clean API. The required value is explicit, the flexible message is easy to pass, and extra context remains named. That makes the function convenient without hiding the meaning of the important parts.
A Real Example: Decorators
Decorators often use *args and **kwargs because the wrapper function does not know the exact signature of the function it wraps. It needs to accept any arguments, pass them to the original function, and return the result. This is one of the most common professional uses of the pattern.
def trace_call(function):
def wrapper(*args, **kwargs):
print(f"Calling {function.__name__}")
result = function(*args, **kwargs)
print("Done")
return result
return wrapper
@trace_call
def multiply(a, b):
return a * b
print(multiply(4, 5))Without *args and **kwargs, the decorator would only work for one specific function signature. With them, it can wrap many different functions. This is why understanding argument forwarding is important before writing decorators, middleware, callbacks, or reusable utilities.
Common Mistakes to Avoid
The first mistake is using *args and **kwargs when explicit parameters would be clearer. Flexibility is not always better. If a function always needs email, password, and remember_me, write those parameters directly. Future readers should not have to guess which keys are expected inside kwargs.
The second mistake is forgetting that args is a tuple. You cannot modify it in place. If you need a mutable collection, convert it to a list first. This is related to the difference between mutable and immutable data structures, which also matters when working with Python lists and tuples.
The third mistake is forgetting to unpack an existing list or dictionary. If a function expects three separate values and you pass one list, Python treats that list as one argument. Use *list_name to unpack positional values and **dict_name to unpack keyword values. Many beginner errors come from mixing these two ideas.
Type Hints with args and kwargs
You can use type hints with *args and **kwargs, but the syntax can surprise beginners. When you write *args: int, you are saying that each positional argument should be an integer. When you write **kwargs: str, you are saying that each keyword value should be a string. The keys are still strings because Python keyword argument names are strings.
def sum_integers(*args: int) -> int:
return sum(args)
def build_labels(**kwargs: str) -> list[str]:
return [f"{key}: {value}" for key, value in kwargs.items()]Type hints make flexible functions easier to understand, especially in larger projects. They also help editors and static analysis tools detect mistakes earlier. For more examples, read this guide to type hints in Python.
When Not to Use args and kwargs
Do not use *args and **kwargs just because they look advanced. They can make code harder to read when the function has a known, stable interface. Explicit parameters are better for validation, documentation, autocomplete, and onboarding. A function signature should communicate what the function expects.
Use these tools when the flexibility is real: forwarding arguments, building wrappers, accepting optional configuration, creating reusable utilities, or processing variable-length input. Avoid them when they only hide a messy design. If a function becomes hard to understand because it accepts everything, consider splitting it into smaller functions. This connects to broader Python best practices.
Final Checklist
Use *args for extra positional arguments. Use **kwargs for extra keyword arguments. Remember that args becomes a tuple and kwargs becomes a dictionary. Keep the parameter order correct. Prefer explicit parameters when the expected inputs are known. Use unpacking when you already have values in lists, tuples, or dictionaries. Add type hints and documentation when flexible arguments might be unclear.
Once you understand *args and **kwargs, you will read professional Python code with much more confidence. They appear in decorators, web frameworks, testing tools, data libraries, class constructors, and reusable APIs. The syntax looks unusual at first, but the idea is simple: collect what you do not know in advance, and pass it forward safely when needed.






