๐Ÿ“ฅ Python

Python Function Arguments โ€” Every Way to Pass Data In, Explained Properly

Positional, keyword, default, *args, **kwargs, positional-only, keyword-only, and argument unpacking โ€” the exact order rules Python enforces, and the specific TypeErrors you'll hit while learning them.

๐Ÿ“…

Last Updated

March 2026

โฑ๏ธ

Read Time

20 min

๐ŸŽฏ

Level

Beginner

What Are Function Arguments in Python?

A function without inputs can only ever do one fixed thing. Arguments are what let the same function behave differently every time it's called โ€” send_email("priya@example.com", subject="Invoice") and send_email("rohan@example.com", subject="Reminder") run identical code with different data plugged in. Arguments are the entire reason functions are reusable instead of just named blocks that always do the exact same thing.

Here's where Python genuinely stands apart from most beginner-friendly languages: it gives you five distinct ways to pass arguments into a function โ€” positional, keyword, default-valued, arbitrary positional (*args), and arbitrary keyword (**kwargs) โ€” and they can all coexist in a single function signature, in a specific, non-negotiable order. That flexibility is genuinely powerful once it clicks. Before it clicks, it's the source of some of the most confusing tracebacks a beginner will encounter, mostly because the error messages assume you already know the vocabulary they're using.

I still remember staring at TypeError: create_user() got multiple values for argument 'role' for a solid ten minutes before realising the actual problem: I'd passed role both positionally and as a keyword argument in the same call, and Python had absolutely no idea which one I actually meant. The error message is accurate. It's just written assuming you already know exactly what 'multiple values for one argument' means mechanically โ€” which, the first time you see it, you don't.

This guide walks through every argument-passing style Python supports, the exact order rules the interpreter enforces, argument unpacking with * and ** at the call site, and โ€” because it trips up almost everyone eventually โ€” how Python actually passes arguments under the hood, which is neither strictly 'by value' nor strictly 'by reference' the way those terms work in C or Java.

History โ€” How Python's Argument-Passing Syntax Grew Over Time

Python's argument system today is the product of several distinct additions over more than three decades, each solving a specific real-world pain point that developers had already run into.

  • โ–ถ

    1991 โ€” Python 0.9.0 โ€” Basic positional arguments existed from the very first release. Call a function, values get matched to parameters strictly by position, in order โ€” the simplest and oldest form of argument passing in the language.

  • โ–ถ

    Early 1990s โ€” Default Arguments โ€” Default parameter values (def f(x=10):) were present very early on, letting callers omit arguments that have a sensible fallback, without needing separate overloaded function definitions the way C++ or Java would eventually require.

  • โ–ถ

    Early 1990s โ€” Keyword Arguments โ€” The ability to call a function by naming parameters explicitly (greet(name='Priya')) rather than relying purely on position was also part of Python's earliest design โ€” a genuinely progressive feature for its era, when most mainstream languages offered only positional calling.

  • โ–ถ

    Early 1990s โ€” *args and **kwargs โ€” Variable-length argument collection using * and ** prefixes appeared early too, allowing functions like print() itself to accept any number of arguments without a fixed signature.

  • โ–ถ

    2008 โ€” Python 3.0 (PEP 3102) โ€” Introduced keyword-only arguments โ€” parameters that must be passed by name, enforced using a bare * in the function signature. This gave library authors a way to force clarity at call sites for parameters where positional calling would be error-prone or ambiguous.

  • โ–ถ

    2018 โ€” Python 3.8 (PEP 570) โ€” Introduced positional-only parameters, marked using a / in the signature. This let standard library functions (many of which had already behaved this way informally in C implementations) formally declare that certain parameters cannot be passed by keyword โ€” useful for letting a library rename an internal parameter later without breaking anyone who called it positionally.

  • โ–ถ

    2026 โ€” Current state โ€” All five styles โ€” positional, keyword, default, *args, **kwargs โ€” plus positional-only and keyword-only markers, now coexist in one unified, precisely ordered grammar. It's more rules to learn upfront than most beginner tutorials admit, but each one solves a genuinely distinct real problem, not academic completeness for its own sake.

Key Characteristics of Python Function Arguments

Here are the 11 things worth knowing precisely about how Python handles arguments โ€” not the vague version, the exact one:

๐Ÿ“
Positional Matching by Order

Arguments passed without a name are matched to parameters strictly left to right, in the order the function defines them. greet('Priya', 25) binds 'Priya' to the first parameter and 25 to the second, regardless of their names.

๐Ÿท๏ธ
Keyword Matching by Name

Arguments passed as name=value are matched by that exact name, regardless of position in the call. greet(age=25, name='Priya') works identically to greet('Priya', 25), because both keyword arguments are matched by name, not position.

๐ŸŽ›๏ธ
Defaults Fill Gaps, Not Override Them

A default value is only used when the caller omits that argument entirely. Passing any value at all for that parameter โ€” even an 'empty' one like None โ€” always overrides the default; Python never silently falls back to a default just because a value looks unusual.

๐Ÿ“ฆ
*args Collects Into a Tuple

Any extra unnamed arguments beyond the explicitly listed parameters get gathered into a tuple accessible inside the function under whatever name follows the asterisk โ€” conventionally args, though any valid name works.

๐Ÿ—ƒ๏ธ
**kwargs Collects Into a Dictionary

Any extra named arguments not matching an explicit parameter get gathered into a dictionary, with argument names as keys. Conventionally called kwargs, though again the name itself is just a convention, not a requirement.

๐Ÿ”ข
Strict Required Order in Signatures

A function signature must follow this exact order: positional-only, then standard positional-or-keyword, then default-valued, then *args, then keyword-only, then **kwargs. Violating this order raises SyntaxError immediately, before the function is ever called.

๐Ÿšซ
Can't Mix the Same Argument Twice

Supplying a value for the same parameter both positionally and by keyword in one call raises TypeError: got multiple values for argument, because Python genuinely cannot tell which value you intended.

๐Ÿ”“
Argument Unpacking at Call Sites

A single asterisk before a list or tuple (*my_list) unpacks it into separate positional arguments at the call site. A double asterisk before a dictionary (**my_dict) unpacks it into separate keyword arguments.

๐Ÿงท
Positional-Only Parameters (/)

Parameters listed before a bare / in the signature can only be passed positionally โ€” attempting to pass them by keyword raises TypeError, even if the name matches exactly.

๐Ÿ”
Keyword-Only Parameters (*)

Parameters listed after a bare * (or after *args) in the signature must be passed by keyword โ€” attempting to pass them positionally raises TypeError, regardless of correct ordering.

๐Ÿ”—
Objects Passed by Reference, Not Copied

Python doesn't copy argument values into the function; it passes a reference to the same object. Mutating a mutable argument (like appending to a list) inside a function affects the original object the caller still holds โ€” reassigning the parameter to a new object does not.

How Python Matches Arguments to Parameters โ€” Flowchart

Every single function call goes through the same internal matching process before the function body even starts running โ€” positional arguments get slotted in first, keyword arguments get matched by name, defaults fill whatever's left unfilled, and anything extra gets swept into *args or **kwargs. Here's that exact sequence.

๐Ÿ“ž Function Calledwith positional and/or keyword args
processes left to right
๐Ÿ“ Bind Positional ArgsMatched left-to-right by order
then
๐Ÿท๏ธ Bind Keyword ArgsMatched by exact parameter name
checks
โ“ Same Param Bound Twice?Positionally AND by keyword
if yes
๐Ÿ’ฅ TypeError Raisedgot multiple values for argument
โ“ Any Required Param Unfilled?No default, no value given
if yes
๐Ÿ’ฅ TypeError Raisedmissing required positional argument
๐ŸŽ›๏ธ Apply DefaultsFill remaining gaps with default values
then
๐Ÿ“ฆ Collect ExtrasLeftover positional โ†’ *args, leftover keyword โ†’ **kwargs
binding complete
โœ… All Parameters BoundFunction body begins executing

Code Execution Flow โ€” from source to output

Key insight: this entire binding process happens before a single line inside the function body runs. If it fails at any decision point, the function never executes at all โ€” you get a clean traceback pointing at the call site, not a partially-run function with confusing half-completed side effects.

How Arguments Actually Work โ€” Order Rules, Unpacking, and Reference Semantics

Three things separate someone who's memorised *args and **kwargs as magic syntax from someone who actually understands argument passing: the strict ordering rule for signatures, how unpacking works at the call site (not just the definition), and what 'passed by reference' really means in Python โ€” because it is genuinely not the same as either C's pass-by-value or Java's pass-by-reference, despite people casually using both terms.

๐Ÿ”ข The Non-Negotiable Signature Order

A full Python function signature, using every available feature, follows exactly this order: def f(pos_only, /, standard, default=1, *args, kw_only, **kwargs):. Positional-only parameters come first, separated by a bare /. Then standard positional-or-keyword parameters. Then defaulted parameters. Then *args for extra positional values. Then keyword-only parameters, which can appear either after a bare * or right after *args itself. Finally **kwargs, always last. Writing a parameter with a default before one without a default โ€” def f(a=1, b): โ€” raises SyntaxError: parameter without a default follows parameter with a default immediately, because Python can't figure out what should happen if a caller supplies only one positional value.

๐Ÿ”“ Unpacking at the Call Site โ€” Where * and ** Actually Shine

The * and ** prefixes aren't only for collecting arguments inside a definition โ€” they also work in reverse, at the call site, to spread out an existing collection. If you have coordinates = (10, 20) and a function def move(x, y):, calling move(*coordinates) unpacks the tuple into two separate positional arguments โ€” exactly equivalent to move(10, 20). Similarly, if user_data = {'name': 'Priya', 'age': 25} and a function def create_user(name, age):, calling create_user(**user_data) unpacks the dictionary into keyword arguments โ€” exactly equivalent to create_user(name='Priya', age=25). This pattern shows up constantly when forwarding arguments between wrapper functions and the functions they wrap, which is exactly how most decorators are implemented internally.

๐Ÿ”— Pass-by-Object-Reference โ€” Neither Value Nor Reference, Exactly

This confuses developers coming from both C-style and Java-style backgrounds, because Python's actual behaviour is its own thing. When you pass an argument, Python passes a reference to the same underlying object โ€” no copy is made. If that object is mutable (a list, dict, or set) and the function modifies it in place โ€” my_list.append(4) โ€” the caller's original object changes too, because there's only ever been one object the whole time. But if the function reassigns the parameter name to a brand new object โ€” my_list = [1, 2, 3] โ€” that reassignment only affects the local name inside the function; the caller's original variable still points at whatever it pointed at before. Immutable objects (strings, integers, tuples) behave as if passed by value in practice, simply because you can't mutate them in place at all โ€” any 'change' to a string parameter is actually creating a new string object and rebinding the local name to it.

Simple rule to remember: positional binds by order, keyword binds by name, defaults fill gaps, *args/**kwargs sweep up the leftovers, and mutating a mutable argument in place affects the caller โ€” reassigning it does not.

Positional vs Keyword vs Default vs *args vs **kwargs โ€” Key Differences

Each argument style solves a genuinely different problem. This table lines up exactly what each one does, how it's written, and when to reach for it.

Argument TypeDefinition SyntaxCall SyntaxMatched ByTypical Use Case
Positionaldef f(a, b):f(1, 2)Order/positionSimple, obvious argument order (coordinates, small math functions)
Keyworddef f(a, b):f(b=2, a=1)Exact nameImproving call-site readability, especially with several arguments
Defaultdef f(a, b=10):f(5) or f(5, 20)Name, with fallbackOptional settings with a sensible common value
*argsdef f(*args):f(1, 2, 3, 4)Position, collected into a tupleUnknown number of positional inputs (e.g. sum of any count of numbers)
**kwargsdef f(**kwargs):f(x=1, y=2, z=3)Name, collected into a dictFlexible configuration-style calls, forwarding to another function
Positional-onlydef f(a, b, /):f(1, 2) only โ€” f(a=1) failsOrder only, keyword forbiddenLibrary APIs where parameter names may change later
Keyword-onlydef f(*, a, b):f(a=1, b=2) only โ€” f(1, 2) failsName only, position forbiddenForcing clarity for easily-confused boolean/config flags

Passing Arguments โ€” Python vs Other Languages

Python's argument flexibility is genuinely unusual compared to statically typed languages, and the contrast explains a lot about why Python APIs tend to look different from Java or C++ ones.

FeaturePythonJavaJavaScriptC++
Keyword argumentsโœ… Native, widely usedโŒ Not supported nativelyโš ๏ธ Simulated via destructured object paramsโŒ Not supported natively
Default argumentsโœ… Native supportโŒ Requires method overloadingโœ… Native support (ES6+)โœ… Native support
Variable-length args*args / **kwargsVarargs: Type... nameRest parameters: ...argsVariadic templates / initializer_list
Positional-only enforcementโœ… Via / marker (3.8+)N/A โ€” all params positional by natureN/A โ€” all params positional by natureN/A โ€” all params positional by nature
Keyword-only enforcementโœ… Via * markerโŒ Not applicableโš ๏ธ Simulated via object destructuringโŒ Not applicable
Argument passing modelPass by object referencePass by value (references are the value for objects)Pass by value (references are the value for objects)Pass by value, or explicitly by reference/pointer
Call-site unpacking*list, **dictโŒ Not supportedโœ… Spread operator ...arrayโŒ Not supported directly

Here's my honestly opinionated take: Python's native keyword arguments are one of its most underrated design decisions, and I genuinely miss them whenever I write Java. A call like create_report(start_date='2026-01-01', end_date='2026-03-31', include_charts=True) is self-documenting at the call site โ€” you don't need to open the function definition to know what each value means. Java developers simulate this with builder patterns or wrapper config objects, which works, but it's noticeably more ceremony to get the same clarity Python gives you for free with plain keyword syntax.

Advantages and Disadvantages of Python's Argument System

This much flexibility is a genuine strength, but it comes with real trade-offs, especially around clarity and API design discipline.

โœ… Advantages
Self-Documenting Call SitesKeyword arguments let a function call read almost like plain English โ€” create_user(name='Priya', is_admin=False) needs no lookup to understand.
Genuinely Flexible APIsCombining defaults, *args, and **kwargs lets one function signature gracefully support a huge range of calling styles without needing overloaded variants.
No Overloading Ceremony RequiredWhere Java needs several method signatures for optional parameters, Python handles the same cases with a single function and default values.
Clean Argument Forwarding*args and **kwargs at both definition and call sites make wrapper functions and decorators that forward arguments to another function remarkably concise.
Enforceable Clarity Where It MattersKeyword-only parameters let API designers force callers to be explicit about easily-confused arguments, preventing accidental positional mix-ups.
Efficient Passing of Large ObjectsPassing by object reference means large lists or dictionaries aren't copied on every function call, which matters for performance with big data structures.
โŒ Disadvantages
Genuinely Confusing Error Messages at FirstTypeError: got multiple values for argument and missing required positional argument are accurate but assume vocabulary beginners haven't learned yet.
Strict, Easy-to-Violate Order RulesThe required positional-only โ†’ positional-or-keyword โ†’ defaults โ†’ *args โ†’ keyword-only โ†’ **kwargs order is a lot to memorise, and violating it raises SyntaxError before the function even runs.
Mutable Argument Mutation SurprisesPassing a list or dict and mutating it inside a function silently changes the caller's original object, which surprises developers expecting value-like copy semantics.
**kwargs Can Hide TyposA misspelled keyword argument intended for a specific parameter can silently get swept into **kwargs instead of raising an error, especially in functions that don't validate their kwargs contents.
Overuse Reduces ReadabilityA function signature drowning in *args, **kwargs, and a dozen optional keyword parameters can become genuinely hard to understand from the outside, defeating the self-documenting benefit entirely.
No Compile-Time Argument Type CheckingPassing the wrong type of value to any argument style won't be caught until that specific line actually executes, unlike statically typed languages that catch mismatches earlier.

Argument Binding Architecture โ€” From Call Site to Local Scope

The diagram below shows exactly where each argument style lives in the pipeline between writing a function call and that function's body actually starting to run.

Call Site (Your Code)
Positional values: f(1, 2)Keyword values: f(a=1, b=2)Unpacked collections: f(*list, **dict)
CPython Argument Parser
Reads function's __code__ signatureValidates order and countDetects duplicate bindings
Binding Resolution
Positional-only slots filled firstStandard params matched by position or nameDefaults applied for unfilled gapsExtras swept into *args / **kwargs
Error Path (On Failure)
TypeError: missing required positional argumentTypeError: got multiple values for argumentTypeError: got an unexpected keyword argument
Local Scope Created
All parameters now bound as local variablesMutable arguments reference original objectsImmutable arguments behave value-like
Function Body Executes
Runs using the bound local variablesMutations to mutable args affect caller's objectsReassignments stay local only

Architecture Diagram

Every Argument Style in One File โ€” Plus the Errors They Produce

Here's a function using every style together, followed by the two most common argument-related errors and exactly what triggers them.

๐Ÿ Pythonall_argument_styles.py
def build_profile(user_id, /, name, role="member", *tags, verified=False, **extra_info):
    print(f"ID: {user_id} (positional-only)")
    print(f"Name: {name}")
    print(f"Role: {role}")
    print(f"Tags: {tags}")
    print(f"Verified: {verified} (keyword-only)")
    print(f"Extra info: {extra_info}")

build_profile(101, "Ananya", "admin", "beta-tester", "early-access", verified=True, city="Pune")

Output

ID: 101 (positional-only) Name: Ananya Role: admin Tags: ('beta-tester', 'early-access') Verified: True (keyword-only) Extra info: {'city': 'Pune'}

Now the two errors that trip up almost everyone at some point โ€” reproduced exactly:

๐Ÿ Pythonargument_errors.py
def create_user(name, role="user"):
    print(f"{name} created with role {role}")

# Error 1: same argument supplied twice
create_user("Rohan", "admin", role="editor")

# Error 2: a required argument never supplied
# create_user(role="admin")

Output

Traceback (most recent call last): File "argument_errors.py", line 5, in <module> create_user("Rohan", "admin", role="editor") TypeError: create_user() takes from 1 to 2 positional arguments but 3 were given

That specific error happens because "admin" was already passed positionally for role, and then role="editor" tried to bind it again by keyword โ€” but since there are only 2 total parameters and 3 positional-looking values were supplied, Python actually reports the positional-count mismatch first. Removing the extra positional value and calling create_user("Rohan", role="editor") instead resolves it cleanly. The commented-out second case โ€” calling with only role="admin" and no name โ€” would raise TypeError: create_user() missing 1 required positional argument: 'name', since name has no default and nothing was ever supplied for it.

Practice This Code โ€” Live Editor

Line-by-Line Explanation

  • โ–ถ

    user_id, /, โ€” Everything before the / is positional-only. Calling build_profile(user_id=101, ...) would raise TypeError: build_profile() got some positional-only arguments passed as keyword arguments: 'user_id'.

  • โ–ถ

    name, role="member" โ€” Standard positional-or-keyword parameters, with role defaulting to "member" if omitted.

  • โ–ถ

    *tags โ€” Collects any additional positional arguments beyond name and role into a tuple โ€” here, ('beta-tester', 'early-access').

  • โ–ถ

    verified=False after *tags โ€” This makes verified keyword-only. It cannot be supplied positionally, only as verified=True or similar.

  • โ–ถ

    **extra_info โ€” Collects any remaining keyword arguments not matching a named parameter โ€” here, just city="Pune" โ€” into a dictionary.

Where Argument Flexibility Actually Matters โ€” Real-World Applications

This isn't abstract syntax trivia โ€” Python's argument system shapes how real, widely-used APIs are designed:

  • โ–ถ

    ๐ŸŒ Web Framework Configuration โ€” Django model fields and view functions lean heavily on keyword arguments and defaults โ€” CharField(max_length=100, blank=True, null=False) reads clearly precisely because every value is named at the call site.

  • โ–ถ

    ๐ŸŽจ Decorators and Function Wrapping โ€” Every decorator that needs to work on functions with unknown signatures relies on *args and **kwargs to transparently forward whatever arguments the wrapped function actually needs, without knowing them in advance.

  • โ–ถ

    ๐Ÿ“Š Data Science Library APIs โ€” pandas.read_csv() accepts dozens of optional keyword arguments (sep, header, dtype, na_values) with sensible defaults โ€” exactly the kind of API that would be unworkable without native default and keyword argument support.

  • โ–ถ

    ๐Ÿ”ง Configuration and Settings Objects โ€” Functions that accept **kwargs as a flexible settings bag let calling code pass only the options it cares about, letting the function apply sensible defaults for everything else.

  • โ–ถ

    ๐Ÿงช Test Fixtures and Parametrised Tests โ€” pytest fixtures and parametrised test functions frequently use default arguments and keyword-only parameters to keep test setup explicit and readable, avoiding ambiguous positional test data.

  • โ–ถ

    ๐Ÿ–ผ๏ธ GUI and Plotting Libraries โ€” matplotlib.pyplot.plot() accepts a long tail of optional style-related keyword arguments (color, linewidth, linestyle) โ€” exactly the pattern **kwargs and defaults are built for.

  • โ–ถ

    ๐Ÿ”Œ API Client Wrappers โ€” Functions wrapping third-party API calls often forward **kwargs directly into an underlying HTTP request library, letting callers pass through headers, timeout, or auth options without the wrapper needing to know every possible option in advance.

  • โ–ถ

    ๐Ÿงฎ Mathematical/Statistical Utility Functions โ€” Functions like a custom sum_all(*numbers) or average(*values) use *args to accept any number of numeric inputs without forcing callers to build a list first.

Why This Deserves Its Own Deep Dive, Not Just a Side Note

Most tutorials cover arguments as a quick aside inside a broader 'functions' lesson. Here's why that undersells the topic:

  • โ–ถ

    ๐Ÿ› It's Where a Huge Share of TypeErrors Come From โ€” missing required positional argument, got multiple values for argument, and unexpected keyword argument are three of the most common tracebacks in real Python codebases, and all three are purely argument-binding issues, not logic bugs.

  • โ–ถ

    ๐Ÿ’ผ API Design Interviews Test This Directly โ€” Being asked to design a function signature that accepts flexible configuration, or to explain why a mutable default argument is dangerous, is a genuinely common mid-level interview question.

  • โ–ถ

    ๐Ÿง  It Explains Library Code You Read Daily โ€” Once *args, **kwargs, and keyword-only parameters actually make sense, reading the source of libraries like requests or pandas stops feeling like decoding a puzzle.

  • โ–ถ

    ๐Ÿ”ง It's Foundational for Writing Decorators โ€” Every non-trivial decorator needs to forward arbitrary arguments to the function it wraps, which is impossible to do cleanly without a solid grasp of *args and **kwargs.

  • โ–ถ

    ๐Ÿ”— It Clarifies a Genuinely Common Point of Confusion โ€” The 'pass by reference vs pass by value' confusion is one of the most frequently mis-explained topics online. Getting it right here prevents a category of bugs involving unexpectedly mutated lists and dictionaries.

  • โ–ถ

    ๐Ÿ‡ฎ๐Ÿ‡ณ It's a Standard Deep-Dive Topic in Indian Python Courses โ€” Beyond the basic def introduction, Indian engineering curricula and interview-prep bootcamps typically dedicate a full separate session to *args/**kwargs and argument order โ€” exactly the scope of this guide.

Argument Unpacking โ€” * and ** at the Call Site, Explained Further

Unpacking deserves its own close look, because the same symbols (* and **) mean something different depending on whether they appear in a function definition or at a call site โ€” a distinction that confuses a lot of learners initially.

  • โ–ถ

    In a function definition โ€” *args and **kwargs COLLECT extra arguments into a tuple and dictionary respectively. This is the 'gathering' direction.

  • โ–ถ

    At a function call โ€” *some_list and **some_dict SPREAD an existing collection out into separate individual arguments. This is the 'scattering' direction โ€” the exact opposite operation, using identical symbols.

  • โ–ถ

    Combining both directions โ€” A very common real pattern: def wrapper(*args, **kwargs): return original_function(*args, **kwargs). Here the wrapper collects whatever it's called with, then immediately spreads those same values back out to call another function โ€” this exact line is the backbone of most decorator implementations.

  • โ–ถ

    Unpacking multiple collections in one call โ€” Python allows combining several unpacked collections in a single call: combined = [*list_a, *list_b] merges two lists, and merged = {**dict_a, **dict_b} merges two dictionaries, with keys from dict_b overriding matching keys from dict_a if there's a conflict.

Symbol LocationMeaningExampleResult
*args in a defGathers extra positional args into a tupledef f(*args):args is a tuple inside the function
**kwargs in a defGathers extra keyword args into a dictdef f(**kwargs):kwargs is a dict inside the function
*value at a callSpreads an iterable into positional argsf(*[1, 2, 3])Equivalent to f(1, 2, 3)
**value at a callSpreads a dict into keyword argsf(**{'a': 1, 'b': 2})Equivalent to f(a=1, b=2)
*a, *b combined (list literal)Merges two iterables into one list[*list_a, *list_b]A single flattened list
**a, **b combined (dict literal)Merges two dicts into one{**dict_a, **dict_b}A single merged dict, later keys win on conflict

Python Function Arguments โ€” Interview Questions

These come up constantly in Python interviews, from fresher screening rounds to mid-level backend interviews focused on API design.

Practice Questions โ€” Test Your Understanding

Work through these before checking the answers. Predicting exact output and exact error text is the real test here.

1. What is the output of: def f(*args): return sum(args) \n print(f(1, 2, 3, 4))

Easy

2. What is the output of: def f(**kwargs): return kwargs \n print(f(a=1, b=2))

Easy

3. What happens when you run: def f(a, b, /): return a + b \n print(f(a=1, b=2))?

Medium

4. Trace this: def f(a, b=5, *args): return (a, b, args) \n print(f(1, 2, 3, 4))

Medium

5. What does this print? def f(a, *, b): return a + b \n print(f(1, b=2))

Medium

6. What does this print, and why? def modify(lst): lst.append(4) \n numbers = [1, 2, 3] \n modify(numbers) \n print(numbers)

Medium

7. What does this print, and why does it differ from the previous question? def reassign(lst): lst = [9, 9, 9] \n numbers = [1, 2, 3] \n reassign(numbers) \n print(numbers)

Hard

8. What is the exact error if you call def f(a, b, c): pass as f(1, 2, 3, 4)?

Easy

Conclusion โ€” Master the Order, and the Errors Stop Being Scary

Every one of the confusing tracebacks covered in this guide โ€” missing required positional argument, got multiple values for argument, got an unexpected keyword argument โ€” traces back to the exact same underlying mechanism: Python trying, and failing, to bind the values you supplied to the parameters your function actually declared. Once that binding process is genuinely clear in your head, these errors stop being cryptic and start being precise, useful diagnostics that point you straight at the fix.

If you're a complete beginner, the priority is getting comfortable with plain positional and keyword arguments first โ€” that alone covers the overwhelming majority of function calls you'll write. If you're moving into writing reusable libraries or decorators, the priority shifts toward *args, **kwargs, and unpacking, since forwarding arguments cleanly between functions is where these tools genuinely earn their complexity.

Your SituationWhat to Focus On
Just learning function callsโœ… Positional and keyword arguments โ€” the fundamentals
Function needs sensible optional settingsโœ… Default arguments, never mutable defaults
Unknown number of positional inputsโœ… *args
Flexible configuration-style callsโœ… **kwargs
Forwarding arguments to another functionโœ… *args, **kwargs combined with unpacking at the call site
Designing a library API for othersโš ๏ธ Consider positional-only and keyword-only markers for clarity
Passing a list or dict into a functionโš ๏ธ Remember: mutation affects the caller, reassignment does not
Debugging a confusing TypeErrorโœ… Re-read the exact error text โ€” it names the specific parameter

The habit worth carrying forward: whenever a function signature starts accumulating more than three or four parameters, especially a mix of optional ones, reach for keyword arguments at the call site even where positional would technically work. It costs a few extra characters and buys real clarity for whoever reads that call six months from now โ€” quite possibly you.

Arguments are the actual interface of every function you write. Get the order rules solid, understand what unpacking does in both directions, and know exactly when mutation quietly leaks back to the caller โ€” everything else about calling functions confidently follows from that. ๐Ÿ

Frequently Asked Questions (FAQ)