If you are learning Python, functions feel easy at first. You define them, pass arguments, get results. Then one day you encounter *args and **kwargs, and suddenly function calls look confusing and unpredictable.
This article is designed as one complete learning source. We will start from absolute basics and slowly move toward advanced, production‑ready patterns used in real Python libraries.
By the end of this post, a beginner will understand not just what *args and **kwargs are, but why they exist and how professionals design APIs using them.
We will build everything step by step using one evolving example: create_subway() 🥪
1. What Is a Function?
A function is a reusable block of code that:
- accepts input (arguments)
- performs logic
- optionally returns output
def greet(name):
return f"Hello {name}"
greet("Jazz")
Here:
-
name→ parameter (defined in function) -
"Jazz"→ argument (passed during call)
This distinction matters once arguments become flexible.
2. Positional Arguments (Order Matters)
def create_subway(size, bread):
print(size, bread)
create_subway(30, "multi-grain")
Python assigns values by position:
-
30→size -
"multi-grain"→bread
If you swap them, Python won’t complain — but your logic will break.
3. Keyword Arguments (Name Matters)
create_subway(bread="multi-grain", size=30)
Now:
- Order doesn’t matter
- Names do
Critical Rule (Very Important)
Python always assigns positional arguments first, from left to right, before it processes keyword arguments.
This single rule explains most *args / **kwargs confusion.
4. Why Fixed Arguments Don’t Scale
Imagine a real sandwich order:
- size
- bread
- quantity
- veges
- sauces
- extras
You cannot keep adding parameters forever:
def create_subway(size, bread, veges, sauce, extras, cheese_type, toast_level):
...
This becomes unreadable and fragile.
Python solves this with variable arguments.
5. *args — Variable Positional Arguments
*args collects extra positional arguments into a tuple.
Beginner Version
def create_subway(size, bread, *args):
print(f"Size: {size}")
print(f"Bread: {bread}")
print(f"Extras: {args}")
Call:
create_subway(30, "multi-grain", "cheese", "paneer", "jalapeno")
Internally:
args == ("cheese", "paneer", "jalapeno")
When to Use *args
- Number of values is unknown
- Order matters
- Names don’t matter
6. **kwargs — Variable Keyword Arguments
**kwargs collects extra keyword arguments into a dictionary.
Example
def create_subway(size, bread, **kwargs):
print(f"Size: {size}")
print(f"Bread: {bread}")
print("Options:", kwargs)
Call:
create_subway(
30,
"multi-grain",
veges=["olive", "cucumber"],
sauce=["bbq", "honey-mustard"]
)
Internally:
kwargs == {
"veges": [...],
"sauce": [...]
}
When to Use **kwargs
- Optional configuration
- Named values
- Future‑proof APIs
7. Using *args and **kwargs Together
Correct order must be followed:
def create_subway(size, bread="multi-grain", *args, **kwargs):
...
Order rule:
- Required parameters
- Default parameters
*args**kwargs
8. Why bread Became Positional (Common Confusion)
create_subway(30, 2, bread="honey-bread")
Step-by-step binding
| Position | Parameter | Value |
|---|---|---|
| 1 | size | 30 |
| 2 | bread | 2 |
Python assigns positional arguments first.
Now bread already has a value (2).
When Python sees:
bread="honey-bread"
It raises:
TypeError: got multiple values for argument 'bread'
Nothing is converted. Position alone decides binding.
9. Defensive create_subway()
Beginner‑safe version:
def create_subway(size, bread="multi-grain", *args, **kwargs):
print(f"Size: {size}")
print(f"Bread: {bread}")
if args:
print(f"Quantity: {args[0]}")
veges = kwargs.get("veges", [])
sauces = kwargs.get("sauce", [])
print("Veges:", veges)
print("Sauces:", sauces)
10. Production‑Ready Version (Recommended)
def create_subway(
size: int,
bread: str = "multi-grain",
quantity: int = 1,
*,
veges=None,
sauces=None
):
veges = veges or []
sauces = sauces or []
print("Order Summary")
print("-" * 20)
print(f"Size : {size}")
print(f"Bread : {bread}")
print(f"Quantity : {quantity}")
print(f"Veges : {veges}")
print(f"Sauces : {sauces}")
Call:
create_subway(
30,
bread="honey-bread",
quantity=2,
veges=["olive", "cucumber"],
sauces=["bbq"]
)
Why This Is Production‑Ready
- Keyword‑only arguments (
*) - Type hints
- Explicit API
- No hidden positional traps
11. Real‑World Usage from Popular Libraries
print()
print("Jazz", "Suchi", sep=", ", end="!")
Internally uses:
def print(*args, **kwargs):
...
logging
logger.info("User %s logged in", user_id, extra={"ip": ip})
-
*args→ message formatting -
**kwargs→ metadata
requests
requests.get(
url,
headers={...},
timeout=5,
params={...}
)
Flexible APIs powered by **kwargs.
pandas
df.drop(columns=["A"], inplace=True, errors="ignore")
Keyword arguments allow backward compatibility and clean defaults.
12. Mental Model That Sticks
-
*args→ extra values -
**kwargs→ extra named settings - Positional arguments lock slots first
- Keywords cannot override positions
Final Takeaway
If you understand:
- how Python binds arguments
- why position beats name
- when to use
*argsvs**kwargs
You are no longer a beginner.
You are designing flexible, professional Python APIs — exactly how real libraries do it.
Happy coding 🚀
Top comments (0)