Back to Blog

Arguments vs Parameters in Python — What is the Difference? (Common Questions #1)

Sandy LaneSandy Lane

Video: Arguments vs Parameters in Python — What is the Difference? (Common Questions #1) by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

Quick answer: parameters are the variables in the function definition. Arguments are the values you pass when calling. Same idea seen from two ends.

It is one of those distinctions that sounds nitpicky until you read your first stack trace. "Got 2 positional arguments but expected 1 parameter." Until you know which is which, you can't read that error.

This is Episode 1 of the Common Questions in Python series — short, focused answers to the questions search engines see most often. Three minutes per video, one concept per episode, working code you can copy.

The definitions

Parameter — a variable that appears in the function's signature. It is the placeholder for whatever value the caller will pass.

Argument — the actual value the caller passes when invoking the function.

def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

name and greeting are parameters. They are local variables that exist only inside greet.

greet("Alice")

"Alice" is the argument. It is the actual string that gets bound to the parameter name when the function runs.

The Python docs use the same vocabulary. Error messages use the same vocabulary. Your colleagues will use the same vocabulary. Once you know which is which, every conversation about functions is clearer.

Positional arguments

greet("Alice")
greet("Bob", "Hi")

The most common way to pass arguments. Position determines which parameter each value binds to. "Alice" is in the first position, so it binds to the first parameter (name). "Bob" and "Hi" are in positions one and two, so they bind to name and greeting respectively.

The argument's type doesn't have to match anything explicit — Python is dynamically typed — but the count does. Pass more arguments than the function has parameters, and you get a TypeError: greet() takes 2 positional arguments but 3 were given.

Keyword arguments

greet(greeting="Hey", name="Charlie")

Pass arguments by name instead of by position. The order doesn't matter — greeting is named, so it goes to the greeting parameter regardless of where it appears.

Keyword arguments help readability when:

  • The function has many parameters and the order is hard to remember.
  • A bool argument's meaning isn't obvious from the call site (save(force=True) is clearer than save(True)).
  • You're skipping early parameters and providing later ones.

You can mix positional and keyword arguments — but keyword arguments must come last:

greet("Charlie", greeting="Hey")    # OK: positional then keyword
# greet(greeting="Hey", "Charlie")  # SyntaxError: positional after keyword

This is a hard syntax rule. The interpreter doesn't even let your program start if you violate it.

Default values

def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greeting="Hello" makes the parameter optional. If the caller doesn't provide a value, greeting defaults to "Hello". This is why greet("Alice") works even though greet has two parameters — the second one has a default.

A pitfall: don't use mutable values as defaults.

def add_item(item, items=[]):    # WRONG
    items.append(item)
    return items

The list is created once when the function is defined, not each time it's called. Two consecutive calls share the same list. Use None as a sentinel:

def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

*args: collecting extra positional arguments

def show_args(*args):
    print(f"Got {len(args)} args: {args}")

show_args(1, 2, 3)
# Got 3 args: (1, 2, 3)

The *args parameter collects all extra positional arguments into a tuple. The name args is conventional but not magic — *things works the same way. The asterisk is what does the collecting.

Use *args when the function should accept a variable number of values:

  • print(*values) works because print uses *args (or its equivalent in C).
  • max(1, 2, 3) and max(numbers) both work because max uses *args.

**kwargs: collecting extra keyword arguments

def show_kwargs(**kwargs):
    print(f"Got kwargs: {kwargs}")

show_kwargs(name="Eve", age=30)
# Got kwargs: {'name': 'Eve', 'age': 30}

The **kwargs parameter collects extra keyword arguments into a dict. Two asterisks for keyword arguments; one for positional.

Use **kwargs when the function should accept a variable number of named values — typically when forwarding to another function:

def my_request(method, url, **kwargs):
    print(f"{method} {url} with {kwargs}")
    requests.request(method, url, **kwargs)    # forward everything else

The combination of *args and **kwargs lets you write functions that pass arbitrary arguments through to another function. It is the signature of every "wrapper" or "decorator" in Python.

The full vocabulary

Putting it all together:

def fn(positional, keyword="default", *args, **kwargs):
    pass

fn(1)                         # positional only
fn(1, "x")                    # positional + named-but-positional
fn(1, keyword="x")            # positional + keyword
fn(1, "x", 2, 3)              # extras go into args
fn(1, foo="bar", baz="qux")   # extras go into kwargs
fn(1, "x", 2, 3, foo="bar")   # mix everything

Five parameter kinds: positional, default, args, keyword-only (with a * before them), *kwargs. Five argument forms: positional, keyword, unpacked-positional (*list), unpacked-keyword (**dict), and combinations.

For most code you write, the simple positional + keyword + default values cover 95% of cases. args and *kwargs come in when you need flexibility — which is most of the time once you start writing libraries or wrappers.

Common mistakes

Putting positional arguments after keyword ones. fn(name="Alice", "Bob") is a SyntaxError. Keywords must come last.

Using mutable defaults. def fn(x=[]) shares the list across calls. Use None as a sentinel.

Confusing *args with args*. The asterisk goes before the name. *args is a parameter that collects; args* is a syntax error.

Forgetting ** to unpack a dict. fn(my_dict) passes the dict as one positional argument. fn(**my_dict) unpacks it into keyword arguments.

Recap

Parameters are variables in the function definition. Arguments are values passed at call time. Positional arguments bind by order; keyword arguments bind by name. Defaults make parameters optional. *args collects extra positionals into a tuple; **kwargs collects extra keywords into a dict. Keyword arguments come after positional ones.

Now when Python yells at you about "expected 2 arguments, got 3" or "unexpected keyword argument," the message reads as a sentence instead of as runes.

Next episode: what are functions? A back-to-basics look at why functions exist, when to write them, and how def actually works.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.