Function Arguments Interview Questions & Answers

6 questions Updated 2026-06-18

Python interview questions on args and kwargs, positional vs keyword arguments, defaults, keyword-only and positional-only parameters, unpacking at the call site, and parameter ordering rules.

In a function signature, *args collects extra positional arguments into a tuple, and **kwargs collects extra keyword arguments into a dict. They let a function accept a variable number of arguments. The names are convention — only the */** matter.

def log(level, *args, **kwargs):
    print(level, args, kwargs)

log("INFO", 1, 2, user="ada", id=7)
# INFO (1, 2) {'user': 'ada', 'id': 7}

args is always a tuple and kwargs always a dict. They're essential for writing wrappers/decorators that forward arbitrary arguments through to another callable.

Positional arguments are matched to parameters by their order. Keyword arguments are matched by name (param=value), so order doesn't matter among them. At the call site you can mix the two, but every positional argument must come before any keyword argument.

def greet(name, greeting): ...

greet("Ada", "Hello")              # both positional
greet(name="Ada", greeting="Hi")   # both keyword (order free)
greet("Ada", greeting="Hi")        # mix: positional first
greet(name="Ada", "Hi")            # SyntaxError — kw before positional

Keyword arguments make calls self-documenting and let you skip over earlier parameters that have defaults. Use them for clarity on boolean flags and long argument lists.

A parameter with name=value is optional — if the caller omits it, the default is used. Defaults are evaluated once, when the def runs, so using a mutable default ([], {}) is a classic trap: the same object persists across calls.

def connect(host, port=5432, timeout=30):
    ...
connect("db")                 # uses port=5432, timeout=30
connect("db", timeout=5)      # override one by keyword

def bad(item, bucket=[]):     # DON'T — shared list
    bucket.append(item); return bucket

The safe pattern for a mutable default is bucket=None plus if bucket is None: bucket = [] inside the body. Parameters with defaults must come after those without.

Any parameter listed after a bare * (or after *args) is keyword-only — it can never be passed positionally and must be named at the call site. This forces clearer calls and prevents accidental positional mistakes.

def make_request(url, *, timeout=30, verify=True):
    ...

make_request("http://x", timeout=5)     # OK
make_request("http://x", 5)             # TypeError — timeout is kw-only

Keyword-only parameters are great for optional flags whose meaning isn't obvious from position (especially booleans). The lone * is just a separator; it doesn't collect anything.

Parameters listed before a / in the signature are positional-only (Python 3.8+) — they cannot be passed by keyword. This is useful for APIs where the parameter name is an implementation detail you don't want callers to depend on.

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

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

It also frees those names for use in **kwargs. Many built-ins (like len, pow) are positional-only. Combined with *, a signature can have positional-only, normal, and keyword-only sections.

A full signature follows a fixed order: positional-only /, then normal, then *args, then keyword-only, then **kwargs. Within each group, parameters without defaults precede those with defaults.

def f(pos_only, /, normal, *args, kw_only, **kwargs):
    ...

# call-site unpacking mirrors this:
def g(a, b, c): ...
nums = [1, 2, 3]
g(*nums)                 # spread list into positionals
g(**{"a": 1, "b": 2, "c": 3})   # spread dict into keywords

Getting the order wrong is a SyntaxError. The *// markers partition the signature; remember the sequence "positional-only → normal → varargs → keyword-only → varkwargs."

Practice tests are coming soon

Get notified when interactive mock interviews and quizzes launch.