Skip to contentSkip to navigation
Module 05 / Lesson 02intermediate20 min read

Positional & Keyword Arguments

Configure required parameters, named keyword calls, default values, and avoid the mutable default trap.

Prerequisites: functions
Architecture: Parameter Binding Protocol & Sentinel Defenses

01 / Mental Model: Mail Slots, Named Tags & The Mutable Trap

Think of a function's parameter signature as a contract specifying incoming mail delivery slots. Positional arguments are like packages delivered strictly by slot order (slot 1, slot 2). Keyword arguments are packages addressed with explicit recipient tags (e.g. to: 'port'), allowing them to arrive in any sequence. Default values provide pre-filled package contents if a slot is left vacant.

Interactive Parameter Visualizer
Function Signature: def configure(host, port=80, *, ssl=True):
                                  ▲      ▲          ▲
                                  │      │          └─ Keyword-Only (must be named)
                                  │      └─ Optional with Default (80)
                                  └─ Required Positional

Call 1: configure('api.internal')            => host='api.internal', port=80, ssl=True
Call 2: configure('api.internal', 443)       => host='api.internal', port=443, ssl=True
Call 3: configure('api.internal', ssl=False) => host='api.internal', port=80, ssl=False
Call 4: configure(port=8080, host='10.0.0.1') => host='10.0.0.1', port=8080, ssl=True

At the call site, positional arguments must strictly precede any keyword arguments. Keyword arguments can be reordered freely.

02 / Syntax Anatomy: Positional-Only, Defaults, *args & **kwargs

#   [1]         [2]   [3]         [4]      [5]    [6]          [7]
    def request(verb, /, timeout=30, *headers, auth, debug=False, **extra):
        """Standard HTTP request dispatcher with structured signature."""
[1] verb:

Positional-only parameter (preceding /). Cannot be called as verb='GET'.

[2] / delimiter:

PEP 570 positional boundary. All prior parameters are positional-only.

[3] timeout=30:

Standard parameter with default value stored in __defaults__.

[4] *headers:

Variadic positional arguments gathered into an immutable tuple.

[5] auth:

Required keyword-only argument (following *headers). Must be named.

[6] debug=False:

Keyword-only with default stored in __kwdefaults__.

[7] **extra:

Variadic keyword arguments collected into a mutable dict.

03 / Under the Hood: CPython Argument Binding Protocol (Objects/call.c)

CPython Binding Stages
Stage 1: Positional Argument Placement

CPython inspects co_argcount and maps positional arguments sequentially into the newly provisioned frame's fastlocals array (fastlocals[0 .. N-1]). Positional-only parameters (co_posonlyargcount) are validated to ensure no keyword name attempts to bind to them.

fastlocals[0] = evaluated_arg_0
fastlocals[1] = evaluated_arg_1

04 / Comparison Matrix: Parameter Flavors & Call Strategies

Parameter TypeMatching MechanismCall SyntaxOrder DependencyPrimary Advantage
PositionalIndex order in argument listfn(1, 2)Strictly orderedFastest CPU execution; concise
KeywordParameter name lookupfn(b=2, a=1)Order-independentSelf-documenting; safe refactoring

05 / Syntax Reference: Parameter Signatures & Constraints

Parameter TypeSyntaxDefinition OrderCall SyntaxNotes
Positional-Onlydef f(a, b, /):First, before '/'f(1, 2) (cannot use a=1)Introduced in Python 3.8 (PEP 570); callers cannot specify argument name.
Standard Positional / Keyworddef f(a, b=0):After '/', before '*'f(1, 2) or f(a=1, b=2)Most common parameter type; default parameters must follow non-defaults.
Variadic Positional (*args)def f(*args):After standard parameters, before keyword-onlyf(1, 2, 3, 4)Gathers surplus positional arguments into an immutable tuple.
Keyword-Onlydef f(*, key=val): or def f(*args, key=val):After '*' or '*args', before '**kwargs'f(key=val) (cannot pass positionally)Prevents callers from relying on argument position; stored in __kwdefaults__.
Variadic Keyword (**kwargs)def f(**kwargs):Must be the very last parameterf(x=1, y=2, z=3)Gathers all surplus keyword arguments into a mutable dictionary.

06 / Core Code Walkthroughs: 7 Argument Design Patterns

Positional vs. Keyword Arguments and Mixing Rules

Calling functions using positional ordering, named keywords, and adhering to Python's ordering constraints.

Positional vs. Keyword Arguments and Mixing Rules Run cleanly to earn +1 XP
def configure_server(host, port, ssl_enabled, timeout=30):
    return f'Connecting to {host}:{port} (SSL: {ssl_enabled}, Timeout: {timeout}s)'

# 1. Pure positional call (matched by order)
res1 = configure_server('10.0.0.1', 8080, True)
print('Positional:', res1)

# 2. Pure keyword call (order does not matter!)
res2 = configure_server(port=443, ssl_enabled=True, host='api.service.io', timeout=60)
print('Keyword:', res2)

# 3. Mixed call: positional arguments MUST precede keyword arguments
res3 = configure_server('127.0.0.1', 3000, ssl_enabled=False)
print('Mixed:', res3)
Run cleanly to earn +1 XP: Select Run (or edit code first to experiment)! Runs that end in an error earn no XP.

07 / Interactive Playground: Keyword Arguments & Sentinel Isolation

Run and modify the code below to see the difference between shared mutable default arguments and fresh sentinel allocations.

Run & Experiment with Function Arguments & Sentinels
# 1. Test Sentinel Pattern vs Shared Default
def register_student(name, grades=None):
    if grades is None:
        grades = []
    grades.append(100)
    return {"name": name, "grades": grades}

s1 = register_student("Zara")
s2 = register_student("Kabir")
print("Student 1:", s1)
print("Student 2 (cleanly isolated!):", s2)

# 2. Test Argument Unpacking (*args, **kwargs)
def send_notification(user, message, *, priority="normal", **metadata):
    return {
        "to": user,
        "body": message,
        "priority": priority,
        "meta": metadata
    }

user_data = {"priority": "urgent", "channel": "sms", "retry": True}
notif = send_notification("alice@py.io", "Server down", **user_data)
print("Notification payload:", notif)

08 / Common Pitfalls & Anti-Patterns

🟢 Beginner Pitfall: The Mutable Default Argument Trap

Default parameter values are evaluated once when the function is defined, not on each function call. Using a mutable object like a list or dictionary as a default causes that object to be shared across all calls that omit the argument, leaking state across calls.

❌ Anti-Pattern:
def record_event(event_id, event_log=[]):
    event_log.append(event_id)
    return event_log

print(record_event(1))  # [1]
print(record_event(2))  # [1, 2] (Shared state bug!)
✅ Idiomatic Solution:
def record_event(event_id, event_log=None):
    if event_log is None:
        event_log = []
    event_log.append(event_id)
    return event_log

print(record_event(1))  # [1]
print(record_event(2))  # [2] (Cleanly isolated!)

🟢 Beginner Pitfall: Placing Positional Arguments After Keyword Arguments

At the call site, all positional arguments must precede any keyword arguments. Placing a positional argument after a keyword argument is a compile-time SyntaxError: positional argument follows keyword argument.

❌ Anti-Pattern:
def send_email(to, subject, body):
    return f'{to}: {subject}'

# SyntaxError: positional argument follows keyword argument
# send_email(to='user@corp.io', 'Urgent', 'Meeting at 2pm')
✅ Idiomatic Solution:
def send_email(to, subject, body):
    return f'{to}: {subject}'

# Correct: all positional first, or all keyword
send_email('user@corp.io', 'Urgent', body='Meeting at 2pm')

🟡 Intermediate Pitfall: Passing Multiple Values for the Same Parameter

If you provide a value positionally for a parameter, and also provide an explicit keyword argument for the same parameter name, Python raises TypeError: function() got multiple values for argument.

❌ Anti-Pattern:
def greet(name, greeting='Hello'):
    return f'{greeting}, {name}'

try:
    # 'Alice' fills name positionally; name='Bob' attempts to fill it again!
    greet('Alice', name='Bob')
except TypeError as e:
    print('Multiple values error:', e)
✅ Idiomatic Solution:
def greet(name, greeting='Hello'):
    return f'{greeting}, {name}'

print(greet('Alice', greeting='Hi'))  # Distinct parameters

🟡 Intermediate Pitfall: Defining Default Parameters Before Non-Default Parameters

In a function definition, non-default parameters cannot follow default parameters. If a parameter with a default value were defined before a required parameter, Python would not know how to match positional arguments.

❌ Anti-Pattern:
# SyntaxError: non-default argument follows default argument
# def query(timeout=30, sql):
#     pass
✅ Idiomatic Solution:
# Correct: required non-default parameters come first
def query(sql, timeout=30):
    pass

09 / Edge Cases: Empty Variadics, Non-String Kwargs & Bare Asterisks

1

Empty *args and **kwargs Collections

When no surplus positional or keyword arguments are supplied, *args evaluates to an empty tuple () and **kwargs evaluates to an empty dictionary {}. They are never None.

def inspect_variadic(*args, **kwargs):
    print('args is tuple?', isinstance(args, tuple), 'len:', len(args))
    print('kwargs is dict?', isinstance(kwargs, dict), 'len:', len(kwargs))

inspect_variadic()
Output: args is tuple? True len: 0 kwargs is dict? True len: 0
2

Unpacking Non-String Keys in **kwargs

When unpacking a dictionary with **kwargs, all dictionary keys must be strings. If any dictionary key is an integer or non-string, Python raises TypeError: function() keywords must be strings.

def test_func(**kwargs):
    pass

invalid_dict = {1: 'one', 2: 'two'}
try:
    test_func(**invalid_dict)
except TypeError as exc:
    print('Kwargs error:', exc)
Output: Kwargs error: test_func() keywords must be strings
3

Forwarding Exact Signatures with *args and **kwargs

When building wrapper functions or decorators, forwarding *args and **kwargs transparently preserves all positional and keyword arguments to the wrapped function.

def target(x, y, multiplier=1):
    return (x + y) * multiplier

def wrapper(*args, **kwargs):
    print('Wrapper intercepted call')
    return target(*args, **kwargs)

print('Result:', wrapper(3, 4, multiplier=2))
Output: Wrapper intercepted call Result: 14
4

Bare Asterisk (*) for Pure Keyword-Only Arguments

A bare asterisk (*) in the parameter list does not bind to a variable; it acts as a delimiter indicating that all following parameters MUST be passed as keyword arguments.

def create_table(name, *, primary_key='id', auto_increment=True):
    return f'Table {name} (PK: {primary_key})'

print(create_table('users', primary_key='uuid'))
Output: Table users (PK: uuid)

10 / Memory & Performance Trade-Offs

Positional vs. Keyword Resolution Speed

fastlocals Direct Offsets

Positional arguments map directly to fastlocals array offsets in O(1) time without name resolution. Keyword arguments require hashing and comparing parameter names, incurring slight overhead in high-frequency loops.

Variadic Allocation Overhead (*args & **kwargs)

Heap Allocation

*args allocates a new tuple and **kwargs allocates a new dictionary on each call. If performance is paramount in tight inner loops, explicit named parameters avoid these heap allocations.

Default Values Storage Efficiency

__defaults__ Tuple

Function default values reside in a single tuple (__defaults__) allocated once per function definition, consuming negligible memory regardless of how many times the function is invoked.

PEP 570 Positional-Only Throughput

Vectorcall Protocol

Positional-only parameters (marked with /) allow Python's internal C vectorcall protocol to bypass keyword dictionary inspection completely, yielding fastest possible invocation throughput.

11 / Step-by-Step Problem Solving: Designing Clean Public APIs

Step 1: Order Required Positional Arguments First

Never place parameters with defaults before required parameters. Anchor your function's core inputs (e.g. url, query) as leading positional arguments.

def fetch_resource(endpoint: str, key: str):
    pass

12 / Real-World Applications & Production Architecture

01

Configurable HTTP Client Requests (requests / httpx)

Modern HTTP client libraries define signatures like get(url, params=None, headers=None, **kwargs). Surplus keyword arguments (**kwargs) are passed through to underlying connection and timeout handlers transparently.

02

Flexible Logging and Analytics Trackers

Event tracking SDKs (e.g. telemetry trackers) define track(event_name, *tags, **metadata), allowing callers to record arbitrary metric tags and contextual payload properties without changing SDK method signatures.

03

Parallel Batch Computation with zip() and map()

Scientific and financial computation engines combine zip() and map() to stream multi-feature vectors into model scoring functions with zero-overhead argument unpacking.

13 / Interactive Quiz Booster (17 Questions)

Score: 0 / 17
Question 1 of 17
Which call uses keyword arguments?
easy
Question 2 of 17
What does *args collect?
easy
Question 3 of 17
Why can a default [] be a problem?
medium
Question 4 of 17
What does this print?
def welcome(name, greeting='Hello'):
    print(greeting, name)

welcome('Kai')
easy
Question 5 of 17
What does this print?
def show(item, *extras):
    print(extras)

show('book', 'pen', 'card')
easy
Question 6 of 17
What does this print?
def introduce(name, age):
    print(name, age)

introduce(age=12, name='Mina')
medium
Question 7 of 17
Which call is the bug because a positional argument appears after a keyword argument?
medium
Question 8 of 17
Which parameter definition gives times a default value of 2?
medium
Question 9 of 17
What does this print?
def add_item(item, items=[]):
    items.append(item)
    return items

print(add_item('tea'))
print(add_item('coffee'))
hard
Question 10 of 17
What does **details collect in a function definition?
hard
Question 11 of 17
Where does CPython store a function's default positional argument values at runtime?
hard
Question 12 of 17
What does a bare asterisk `*` in a parameter signature like `def f(a, *, b=1):` enforce?
medium
Question 13 of 17
What happens when you unpack a dictionary with `**` into a function call?
easy
Question 14 of 17
What error occurs if you call `greet('Alice', name='Bob')` when `def greet(name):`?
medium
Question 15 of 17
What does a forward slash `/` in a parameter signature like `def f(x, /, y):` mean?
hard
Question 16 of 17
What is the type of `args` inside a function defined with `def func(*args):` when called with zero arguments `func()`?
easy
Question 17 of 17
What happens if a non-default parameter follows a default parameter in a definition like `def f(a=1, b):`?
medium

14 / Guided Practice Labs & Test Suites

Lab 1

Server Configuration Builder with Options

easy

Write a function make_server(host='127.0.0.1', port=8000, **options) that returns a dictionary containing 'host', 'port', and any additional keyword options passed into the function.

Hint: Initialize cfg = {'host': host, 'port': port}, then call cfg.update(options) and return cfg.
Starter Workspace:
Starter: Server Configuration Builder with Options
def make_server(host='127.0.0.1', port=8000, **options):
    # Return dictionary with host, port, and options
    pass
Verification Test Assertions:
assert make_server() == {'host': '127.0.0.1', 'port': 8000}
assert make_server(port=9000, ssl=True) == {'host': '127.0.0.1', 'port': 9000, 'ssl': True}
assert make_server('0.0.0.0', 5000) == {'host': '0.0.0.0', 'port': 5000}
Lab 2

Safe Accumulator with Sentinel Default

medium

Write a function safe_append(element, collection=None) that safely appends element to collection. If collection is omitted or None, allocate a new list so that subsequent calls do not share state. Return the updated list.

Hint: Check if collection is None, assign collection = [], then call collection.append(element) and return collection.
Starter Workspace:
Starter: Safe Accumulator with Sentinel Default
def safe_append(element, collection=None):
    # Safely append without mutable default trap
    pass
Verification Test Assertions:
a = safe_append('x')
b = safe_append('y')
assert a == ['x'] and b == ['y']
shared = [1, 2]
assert safe_append(3, shared) == [1, 2, 3]
assert shared == [1, 2, 3]
Lab 3

Multi-Sequence Dot Product via zip() and Unpacking

hard

Write a function dot_product(vec_a, vec_b) that computes the dot product of two numerical vectors (sum of products of corresponding elements) using zip() and argument unpacking. If lengths differ, raise a ValueError.

Hint: Compare len(vec_a) != len(vec_b) first, then use sum(x * y for x, y in zip(vec_a, vec_b)).
Starter Workspace:
Starter: Multi-Sequence Dot Product via zip() and Unpacking
def dot_product(vec_a, vec_b):
    # Compute dot product or raise ValueError if lengths differ
    pass
Verification Test Assertions:
assert dot_product([1, 2, 3], [4, 5, 6]) == 32
assert dot_product([2, 0], [0, 5]) == 0
try:
    dot_product([1], [1, 2])
    assert False, 'Should raise ValueError'
except ValueError:
    pass

15 / Debugging Scenarios: Production Post-Mortems

Case 01

State Leakage Across API Requests in Flask/FastAPI Endpoint

Symptom:

Every new user registration received the validation errors of all previously registered users.

Root Cause:

The validation helper was declared as def validate_user(data, errors=[]):. The default errors list persisted across HTTP requests.

Resolution:

Change signature to def validate_user(data, errors=None): with if errors is None: errors = [] to allocate a fresh list per request.

Case 02

TypeError: got multiple values for argument 'format'

Symptom:

A utility function render_doc('report.pdf', 'pdf', format='a4') failed with a confusing TypeError.

Root Cause:

The second parameter was named format, so passing 'pdf' filled format positionally, and format='a4' attempted to fill format again by keyword.

Resolution:

Use distinct parameter names (e.g. extension and page_format) or pass all arguments with matching keywords.

16 / Best Practices, Key Takeaways & Quick Cheatsheet

Engineering Best Practices

Never use mutable objects ([], {}) as default parameter values; always use None as a sentinel.
Place required parameters first, followed by optional parameters with default values.
Use keyword-only parameters (after *) for boolean flags and optional configurations to avoid ambiguous call sites.
Use *args and **kwargs judiciously when building decorators, wrappers, or dynamic dispatchers.
Leverage * and ** argument unpacking at call sites to pass tuples, lists, and dicts directly into structured functions.
Remember that positional arguments must always precede keyword arguments in function calls.

Core Mental Pillars

Pillar 1: Positional arguments match parameters by order; keyword arguments match by parameter name.
Pillar 2: Default values make arguments optional and are evaluated once when the function is defined.
Pillar 3: Always use None as a sentinel default for mutable containers to prevent state leakage between calls.
Pillar 4: *args captures surplus positional arguments as a tuple; **kwargs captures surplus keyword arguments as a dict.
Pillar 5: The * and ** operators unpack iterables into positional arguments and dictionaries into keyword arguments.

Quick Reference Cheatsheet

Keyword Callfn(a=1, b=2)

Matches arguments by explicit parameter name in any order.

Sentinel Defaultdef f(items=None):

Prevents shared memory state bug across multiple calls.

Positional Unpackfn(*coords)

Unpacks elements from list/tuple into positional slots.

Keyword Unpackfn(**config)

Unpacks key-value pairs into matching named parameters.

Frequently Asked Questions

Worked example

A cinema booking: seat number is required, but the snack bundle is optional and can be passed by keyword for clarity.

Example
def book(name, show, snack=None):
    line = f"{name}: {show}"
    if snack:
        line += f" + {snack}"
    return line

print(book("Rafi", "7pm", snack="popcorn"))

How it works, step by step

  1. Positional arguments fill parameters in order: name then show.
  2. snack=... was passed by KEYWORD, so its label makes the call self-documenting.
  3. The default None lets callers skip it.

Your notes

Sign in to keep private notes alongside this lesson.

Sign in to take notes

Positional & Keyword Arguments practice

Sign-in required

Write a function named repeat with parameters word and times=2. Print word repeated times times. Call repeat("go", 3).

Expected output: gogogo

Coding Practice is Locked

Sign in to access the interactive Python editor, write and run code checks against automated tests, use hints, and earn XP.

Practice quiz

Check yourself

15 questions with explanations after submission, so you can correct course as you go.

Positional & Keyword Arguments quiz

Locked

You must be signed in to take this quiz. Sign in to test your understanding, track your answers, and earn XP.

Lesson incomplete

Not completed

Create an ID or sign in to save lesson completion across devices.

Create ID to save
Module lesson progress0/6 complete
Module cheat sheet