Skip to content

Python · Functions

Python Function Arguments Explained — *args, **kwargs, Defaults, and Keyword-Only

4 min read Updated 2026-06-19 Share:

Practice Function Arguments interview questions

Python function arguments, explained

Python's argument system is unusually flexible — positional, keyword, variadic, keyword-only, positional-only — and that flexibility is a frequent interview topic. Get the order and the mutable-default trap right and you'll handle almost any signature question.

Positional vs keyword arguments

When calling a function, you can pass arguments by position or by name:

def greet(name, greeting):
    return f"{greeting}, {name}"

greet("Ada", "Hi")              # positional — order matters
greet(greeting="Hi", name="Ada")   # keyword — order doesn't, clarity wins

Keyword arguments are self-documenting and immune to argument-order mistakes, so prefer them for anything non-obvious (especially booleans and numbers).

Default argument values

A parameter with a default becomes optional. Defaults must come after all non-default parameters:

def connect(host, port=5432, timeout=30):
    ...

connect("db.example.com")             # uses both defaults
connect("db.example.com", timeout=5)  # override just one by name

The mutable default argument trap

This is the single most famous Python gotcha. A default value is evaluated once, when the function is defined — not on each call. A mutable default (like []) is therefore shared across all calls:

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

add_item("a")    # ['a']
add_item("b")    # ['a', 'b']  — the same list persists!

The fix is to default to None and create a fresh object inside:

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

*args — variadic positional arguments

*args collects extra positional arguments into a tuple, letting a function take any number of them:

def total(*nums):
    return sum(nums)

total(1, 2, 3)      # 6 — nums is (1, 2, 3)
total()             # 0 — nums is ()

**kwargs — variadic keyword arguments

**kwargs collects extra keyword arguments into a dict:

def configure(**options):
    return options

configure(debug=True, level=3)   # {'debug': True, 'level': 3}

Together, *args, **kwargs capture any call — which is exactly why decorator wrappers use them to forward arguments transparently.

Keyword-only arguments

A bare * in the signature forces every parameter after it to be passed by keyword. This prevents confusing positional calls:

def create_user(name, *, admin=False, active=True):
    ...

create_user("Ada", admin=True)      # OK
create_user("Ada", True)            # TypeError — admin is keyword-only

Positional-only arguments

A / in the signature (3.8+) forces parameters before it to be passed by position — useful for parameters whose names are implementation details:

def divide(a, b, /):
    return a / b

divide(10, 2)        # OK
divide(a=10, b=2)    # TypeError — a and b are positional-only

The full parameter order

Putting it all together, a complete signature reads in this fixed order:

def f(pos_only, /, normal, *args, kw_only, **kwargs):
    ...
# positional-only | normal | *args | keyword-only | **kwargs

You rarely use all five at once, but knowing the order resolves any "where does this go?" question.

Recap

Arguments can be positional or keyword; defaults come last and are evaluated once at definition — so never use a mutable default, use None and create the object inside. *args gathers extra positionals into a tuple, **kwargs gathers extra keywords into a dict, and together they forward any call. A bare * makes the following parameters keyword-only, a / makes the preceding ones positional-only, and the full order is positional-only → normal → *args → keyword-only → **kwargs.

More ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel