What is the difference between *args and **kwargs, and when would you use each?
*args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict. Use *args when the number of positional inputs is unknown, **kwargs when callers should be able to pass named options without modifying the function signature.
How to think about it
The names args and kwargs are just conventions — what matters is the single * and double ** prefix. Think of * as “unpack this iterable into positional slots” and ** as “unpack this dict into keyword slots.” That mental model works both when defining functions and when calling them.
What’s really being tested
Interviewers want to see you understand why these exist: Python functions have a fixed signature, but sometimes you don’t know how many inputs you’ll get (a logging utility, a decorator that wraps any function, a model trainer that passes config through). *args and **kwargs solve that.
Step 1 — See what they actually contain
Inside the function, args is always a plain tuple and kwargs is always a plain dict. Nothing magical at that point.
Step 2 — Understand the ordering rule
Python enforces a strict order: positional-only → regular → *args → keyword-only → **kwargs. Violating this order is a SyntaxError.
Step 3 — Use the bare * trick
A bare * in the parameter list forces everything after it to be keyword-only, without collecting extra positionals. This is great for functions where you want to prevent positional ambiguity.
The key insight — why forwarding is powerful
The real power shows up in decorators and wrapper functions. When you write wrapper(*args, **kwargs) inside a decorator, you’re forwarding everything the caller passed — including future arguments you haven’t thought of yet. This is why functools.wraps is so useful: it preserves the original signature so the wrapper is transparent.