Python functions become genuinely reusable only when they accept data from the outside world. Arguments are how that data gets in. Knowing which argument type to reach for — and when — is one of the first skills that separates code that works once from code that works everywhere.
Every time you define a function in Python, you decide what information it needs to do its job. That decision shapes how flexible, readable, and reusable your code becomes. This tutorial walks through each argument type from scratch, shows you exactly when each one belongs in your code, and includes hands-on exercises to cement the patterns.
Parameters vs. Arguments: the distinction that matters
These two words get used interchangeably in conversation, but they refer to different things in the code itself. A parameter is the name you list inside the parentheses when you write a def statement. An argument is the concrete value you supply when you call the function. The parameter is the slot; the argument fills the slot.
# 'name' is a parameter — it exists in the function definition
def greet(name):
print(f"Hello, {name}!")
# "Alice" is an argument — it is the value passed at call time
greet("Alice")
# Output: Hello, Alice!
The Python documentation uses "parameter" for the definition side and "argument" for the call side. In practice, people often say "argument" for both. The important thing is recognizing which side of the def you are on.
Build a function definition that takes one parameter called city and prints it:
def, followed by the function name, then parentheses containing the parameter, and ends with a colon. print and return are not part of the signature line — they go in the body below.
The five argument types and when to use each
Python gives you five distinct argument styles. Each solves a different problem. The table below summarizes them before each is covered in depth.
| Type | Syntax in definition | When to use it |
|---|---|---|
| Positional | def f(x, y) | Required values whose order is natural and obvious |
| Keyword | Called as f(x=1, y=2) | When you want to name arguments at the call site for clarity |
| Default | def f(x, y=10) | Optional values with a sensible fallback |
| *args | def f(*args) | Unknown number of positional values |
| **kwargs | def f(**kwargs) | Unknown number of named values |
Positional arguments
Positional arguments are the simplest form. The caller passes values in the exact order the parameters appear in the definition. If the order is wrong, the values land in the wrong slots — Python has no way to know you made a mistake.
def describe_planet(name, diameter_km, moons):
print(f"{name}: {diameter_km:,} km wide, {moons} moon(s)")
describe_planet("Mars", 6_779, 2)
# Output: Mars: 6,779 km wide, 2 moon(s)
# Wrong order — no error, but the output is nonsense
describe_planet(2, "Mars", 6_779)
# Output: 2: Mars km wide, 6779 moon(s)
Use positional arguments when the order is self-evident — like coordinates (x, y) or a range (start, stop). When callers might confuse the order, keyword arguments are a safer choice.
Keyword arguments
Any function that accepts positional arguments can also be called using the parameter names explicitly. When you supply argument names at the call site, order no longer matters. This is especially useful for functions with several parameters where the correct order is not obvious.
def create_user(username, email, role):
return {"username": username, "email": email, "role": role}
# Positional call — relies on caller knowing the order
create_user("jdoe", "jdoe@example.com", "admin")
# Keyword call — order does not matter, intent is explicit
create_user(email="jdoe@example.com", role="admin", username="jdoe")
Default arguments
A default argument gives a parameter a value to fall back on when the caller does not supply one. This makes certain inputs optional without requiring you to write separate versions of the function.
def send_message(recipient, message, priority="normal"):
print(f"[{priority.upper()}] To {recipient}: {message}")
# Caller does not care about priority — default applies
send_message("alice", "Your report is ready")
# Output: [NORMAL] To alice: Your report is ready
# Caller overrides the default
send_message("bob", "Server is down", priority="urgent")
# Output: [URGENT] To bob: Server is down
Never use a mutable object — a list, set, or dictionary — as a default value. Python creates the default once at function definition time, not each time the function runs. Any mutation persists across calls. Use None as the default and create the mutable object inside the function body instead.
# Dangerous — the list is shared across all calls
def add_item_bad(item, container=[]):
container.append(item)
return container
# Safe — a new list is created each time
def add_item_good(item, container=None):
if container is None:
container = []
container.append(item)
return container
*args — variable positional arguments
When you prefix a parameter with a single asterisk, Python collects all extra positional arguments into a tuple under that name. The name args is a convention; the asterisk is what matters. Use *args when you genuinely cannot predict how many values the caller will pass.
def total_score(*scores):
# 'scores' is a tuple of whatever the caller passes
return sum(scores)
print(total_score(88, 92, 76)) # 256
print(total_score(100)) # 100
print(total_score(70, 80, 90, 85, 95)) # 420
**kwargs — variable keyword arguments
Two asterisks collect any extra named arguments into a dictionary. Each key is the argument name the caller used; each value is what was passed. **kwargs appears frequently in wrapper functions and in code that forwards options to another function.
def build_profile(**details):
# 'details' is a dict of whatever named args the caller passed
for key, value in details.items():
print(f" {key}: {value}")
build_profile(name="Sam", role="engineer", team="infra")
# Output:
# name: Sam
# role: engineer
# team: infra
"The best interface is one where each argument name reads like documentation." — Python community convention
move(x, y)). The caller must supply every positional argument or Python raises a TypeError.When to avoid: Functions with many parameters that look similar — a call like
create(True, False, True, 10) is unreadable. Switch to keyword arguments for clarity.
Example:
send("bob", message="Hello", priority="high") is clearer than relying on callers to remember the order.
Constraint: Default arguments must come after all positional (non-default) arguments in the signature.
def f(x=1, y) raises a SyntaxError.
Inside the function:
args is a plain tuple. You can loop over it with for item in args or pass it elsewhere with the unpacking operator *args.
Inside the function:
kwargs is a plain dictionary. Access values with kwargs.get("key", default) to avoid KeyError when a key might be absent.
One line in this function definition will raise a SyntaxError. Which line is it?
email before role="user": def register(username, email, role="user"):. Python cannot allow a default before a non-default because the call site would be ambiguous.
Argument ordering rules
Python enforces a strict ordering rule for function signatures. When you combine multiple argument types, they must appear in this sequence: positional arguments first, then default arguments, then *args, then keyword-only arguments (those defined after *args), and finally **kwargs. Any other arrangement is a SyntaxError.
# Valid signature combining all five argument types
def process(
source, # positional — required
dest="output", # default — optional
*files, # *args — zero or more extra positional values
verbose=False, # keyword-only (comes after *args)
**options # **kwargs — zero or more extra named values
):
pass
# Valid calls
process("data.csv")
process("data.csv", "results/", "extra1.csv", verbose=True)
process("data.csv", compression="gzip", encoding="utf-8")
Any parameter that appears after a *args parameter automatically becomes keyword-only. The caller must supply it by name; Python will not accept it positionally.
How to choose the right argument type for a Python function
Working through a short set of questions at design time keeps function signatures clean and avoids argument-related bugs later.
-
Ask: is the value required every time?
If the function cannot run without the value, make it a positional argument. No default, no flexibility — the caller must supply it or Python raises a
TypeError. This is the right choice for things like a filename, a target URL, or a user ID. -
Ask: is there a sensible fallback?
If many callers will want the same value but an override is occasionally needed, add a default. Common examples include a timeout, a log level, or an output format. Place default arguments after all positional ones, and never use a mutable object as the default.
-
Ask: does the caller need to name the argument?
If the function has several parameters that could be confused with each other, encourage keyword-style calls in your documentation. If you want to require naming at the call site, place the parameter after a bare
*or after*args. Keyword-only arguments raise aTypeErrorif the caller omits the name. -
Ask: is the count unknown?
If the number of values the caller will pass cannot be determined in advance, use
*argsfor positional values or**kwargsfor named values. Both allow zero extra arguments, so a caller who passes nothing will not trigger an error. -
Arrange arguments in the correct order
Place positional arguments first, then defaults, then
*args, then keyword-only arguments, and finally**kwargs. Python checks this at definition time. A violation raises aSyntaxErrorbefore any code runs.
Python Learning Summary Points
- A parameter is the name in the function definition; an argument is the value passed at the call site. The distinction matters when reading error messages.
- Positional arguments are required and order-dependent. Default arguments are optional with a fallback. Keyword style is a calling convention that works with any positional or default parameter.
- *args collects extra positional values into a tuple. **kwargs collects extra named values into a dictionary. Both handle an unknown number of inputs.
- The valid signature order is: positional, default,
*args, keyword-only,**kwargs. Deviating raises aSyntaxError. - Never use a mutable object as a default argument value. Use
Noneand create the object inside the function body.
Choosing the right argument type is a design decision. Positional arguments keep required inputs obvious. Defaults reduce boilerplate for callers who share the common case. Keyword style makes long call sites readable. *args and **kwargs keep your functions open to inputs you have not anticipated. Applying each at the right time produces functions that are both flexible and easy to reason about.
Frequently Asked Questions
A parameter is the variable name listed inside the parentheses when you define a function. An argument is the actual value you pass when you call that function. The parameter is the placeholder; the argument fills it.
Use a default argument when a value is optional and you have a sensible fallback. For example, a greeting function might default to "Hello" so callers who do not care about the greeting style do not need to supply one.
*args lets a function accept any number of positional arguments. Inside the function they are available as a tuple. Use *args when you do not know ahead of time how many values the caller will pass.
**kwargs lets a function accept any number of keyword arguments as a dictionary. The caller names each extra value, and the function receives a dict mapping those names to their values.
Yes. Positional arguments must come first, followed by keyword arguments. Swapping that order raises a SyntaxError.
A TypeError on a function call almost always means the number or names of arguments do not match what the function definition expects. Check that you are passing the right number of values and that keyword argument names are spelled correctly.
Use *args when you want the caller to supply multiple separate values without wrapping them in a list first. Use a list parameter when the data is already a list or when you want to make it obvious the caller should package values together.
The correct order is: positional arguments, then default arguments, then *args, then keyword-only arguments, then **kwargs. Placing a default argument before a positional one raises a SyntaxError.