In Python, what do `*` and `**` do at a call site and inside list or dict literals?
answer
- One direction collects, the other spreads
- The same syntax means different things in two contexts
- Duplicates are fatal in one place, benign in the other
- Key types are restricted only at a call
- A tuple display still needs its comma
basics
~20 sAt a call site * spreads an iterable into positional arguments and ** spreads a mapping into keyword arguments. Inside a literal they splice contents in: [*a, *b] concatenates, {*a, *b} unions, {**m1, **m2} merges with later keys winning.
solid answer
~40 sThese are the *spread* direction of unpacking, generalised by PEP 448 in Python 3.5. At a call, `f(*seq)` passes each item of any iterable as a positional argument and `f(**mapping)` passes each entry as a keyword argument; since 3.5 you may use several of each and mix them with ordinary arguments, evaluated left to right. Inside a display, `[*a, *b]`, `(*a, *b)`, `{*a, *b}` and `{**m1, **m2}` splice the contents of any iterable or mapping into the new object. The two contexts differ on duplicates and key types: a dict literal quietly lets the later key win, while a call raises `TypeError` for a repeated keyword and requires the keys to be strings. A bare `(*a)` is a `SyntaxError` — a tuple display needs the comma, as in `(*a,)`.
code
python · 6 linesdef triage(kind, priority, *, owner="unassigned"):
return f"{kind}/{priority} -> {owner}"
args = ("bug", "high")
extra = {"owner": "queue-a"}
print(triage(*args, **extra)) # bug/high -> queue-ago deeper
Recall what the two symbols do at a call: one spreads a sequence into positional arguments, the other spreads a mapping into keyword arguments. Being able to read f(*args, **kwargs) at a call site is the bar here.
Explain that several of each are allowed since 3.5 and that displays can splice too. The detail that earns the point is the duplicate-key asymmetry: last-wins in a dict display, TypeError at a call.
Show judgement about where the spread form helps and where it hides a contract. Splatting a mapping into a call couples the caller to parameter names; say when you would build an explicit argument list instead, and note the eager consumption of a generator source.
Own the API-shape consequence: functions whose callers routinely splat a config dict have effectively made their parameter names a public schema. Decide whether that boundary should take a structured object instead, and what that costs at the seams.
### Two directions of the same idea Unpacking on the left of `=` *collects*: `first, *rest = xs`. A star in a call or a display does the opposite — it *spreads* an existing iterable or mapping into a position where individual items are expected. PEP 448, "Additional Unpacking Generalizations", landed in Python 3.5 and is what makes the spread form as flexible as it now is; before 3.5 a call could carry only one `*` and one `**`, always last, and displays could not use them at all. ### At a call site `f(*iterable)` feeds each item of the iterable as a separate positional argument, in order. The source can be any iterable, not just a tuple or list — `f(*range(3))` and `f(*"ab")` are fine. `f(**mapping)` feeds each entry as a keyword argument. Since 3.5 you can combine them freely: `g(*[1], *[2, 3])`, `g(*[1, 2], 3)` and `h(**defaults, **overrides)` are all legal, and arguments are evaluated strictly left to right, which is what makes "defaults first, overrides second" a working idiom. Two rules bite here and nowhere else: - **Keyword keys must be strings.** `f(**{1: "x"})` raises `TypeError: keywords must be strings`. A dict *display* has no such rule — `{**{1: "x"}}` is a perfectly good dict — so the restriction belongs to the call, not to `**`. - **A duplicate keyword is an error.** `f(**{"a": 1}, **{"a": 2})` raises `TypeError`, saying the function got multiple values for keyword argument `a`. The same duplication inside a dict display is silently allowed, with the later key winning. That asymmetry is the single most useful thing to know about this syntax, and it is a favourite interview probe: merging two config mappings with `{**base, **overrides}` is a deliberate last-wins merge, while splatting the same two mappings into one call is a hard error. If you want last-wins semantics at a call, merge first and splat once. The `**` source does not have to be a `dict` either. Anything with `keys()` and `__getitem__` — any mapping — can be spread, which is how mapping-like configuration objects get passed straight into a function. ### Inside a display The same operators splice contents into a new container: - `[*a, *b]` builds a list from any two iterables — a concatenation that does not care whether the sources are lists, tuples, sets or generators. - `(*a, *b)` builds a tuple. Note the comma requirement: `(*a,)` is a tuple of a's items, while a bare `(*a)` is `SyntaxError: cannot use starred expression here`, because parentheses alone are grouping, not a tuple display. - `{*a, *b}` builds a set — effectively a union, deduplicating as sets do. Spreading a dict here spreads its **keys**, since iterating a dict yields keys. - `{**m1, **m2}` builds a dict, later entries overwriting earlier ones. You can mix literal entries in: `{**base, "retries": 3}` is a copy-with-override in one expression. Because a display materialises a new object, `[*gen]` fully consumes a generator. `[*a]` is close in effect to calling `list(a)`; the display form's advantage is that it composes — you can interleave several sources and literal elements in one expression. ### Reading it correctly The star in these positions is a syntactic form, not an operator you can apply to an arbitrary expression: it is legal only in a call's argument list, in a display, and in an assignment target list. Since 3.8 it is also allowed unparenthesised in `return` and `yield` statements. Everywhere else you get a `SyntaxError`. Two things this syntax is *not*. It is not the declaration side — a star in a function's parameter list is a different feature with different rules, and the two are frequently confused in interviews because they look identical. And it is not a copy operator: `[*a]` builds a new list holding the *same* element objects, so mutating an element still shows through both containers. All of this is stable from 3.5 through 3.14; the only later change in this area was allowing an unparenthesised starred expression in `return` and `yield` in 3.8.
- Why does `{**m1, **m2}` accept a repeated key while passing the same two mappings into one call raises TypeError?A dict display is defined as building entries in order, so a repeated key simply overwrites — last wins. A call binds each keyword to a distinct parameter, so a repeated keyword is genuinely ambiguous and raises `TypeError: got multiple values for keyword argument`. If you want last-wins behaviour at a call, merge the mappings into one dict first and spread that.
- What is the difference between `(*a)` and `(*a,)` in Python?`(*a,)` is a tuple display containing the items of `a`. `(*a)` is a `SyntaxError` — the parentheses there are grouping, not a tuple constructor, and a starred expression is not a valid standalone expression. The comma is what makes it a tuple, exactly as in any other single-element tuple.
- Does the `**` source at a call site have to be a dict?No. Any mapping works — anything providing `keys()` and `__getitem__` can be spread into keyword arguments, and Python builds the keyword dictionary from those. The only hard constraint is that the keys it yields must be strings; otherwise the call raises `TypeError: keywords must be strings`.
saying these in an interview costs you the question
- Says a call may contain only one star and one double star
- Thinks a dict display raises on a duplicated key
- Claims non-string keys are acceptable as keyword arguments
- Believes `[*a]` deep-copies the elements it splices
- Says `(*a)` builds a tuple without the trailing comma
- Confuses the spread form with a star in a parameter list