Why does {**d1, **d2} merge a shared key silently while f(**d1, **d2) raises TypeError?
answer
- Building a container versus binding parameters
- Displays tolerate a repeat, calls do not
- Last value wins, first position kept
- Multiple values for keyword argument
- Merge first, then unpack once
basics
~20 sA dict display only builds a dict, so a repeated key keeps the last value written. A call binds keyword arguments instead, and binding the same parameter name twice raises TypeError: got multiple values for keyword argument.
solid answer
~40 s`{**d1, **d2}` is a dict display: entries are inserted left to right, so a key present in both ends up with `d2`'s value, exactly as filling a fresh dict from `d1` and then applying `dict.update` with `d2` would. The surviving key keeps the *position* of its first insertion; only the value is replaced. A call is different — every entry of a `**` operand becomes a keyword binding, and a parameter can be bound only once, so `f(**{'x': 1}, **{'x': 2})` raises `TypeError: got multiple values for keyword argument 'x'`. The same error appears for `f(x=1, **{'x': 2})`. A literal repeat like `f(x=1, x=2)` is caught earlier, at compile time, as `SyntaxError: keyword argument repeated`. If overlap is expected, merge first and unpack once: `f(**{**d1, **d2})`.
code
python · 11 linesprint({**{'k': 1, 'j': 0}, **{'k': 2}})
def f(**kwargs):
return kwargs
try:
f(**{'x': 1}, **{'x': 2})
except TypeError as exc:
print('TypeError:', exc)
print(f(**{**{'x': 1}, **{'x': 2}}))go deeper
Recall the two outcomes and do not mix them up: in {**a, **b} the later value wins silently, while f(**a, **b) with a shared key raises a TypeError about multiple values for a keyword argument.
Explain why: a display builds a container and tolerates repeated keys, a call binds parameters and a parameter can be bound only once. Add that the surviving key keeps its first position and that the merge is shallow.
Treat the duplicate-key TypeError as a latent, data-dependent failure that no import-time check catches, and be able to say when to merge deliberately with a display, when to layer with collections.ChainMap, and when a merge has to be recursive.
Own the policy the merge encodes: which layer outranks which, whether precedence should be positional at all, and whether configuration assembly belongs in ad-hoc dict merges rather than one validated settings type with an explicit resolution order.
## The two constructs look alike and mean different things - `{**d1, **d2}` is a *dict display*: it builds a container. - `f(**d1, **d2)` is a *call*: it binds parameters. Building a container tolerates a repeated key because a dict simply holds one entry per key; binding parameters does not, because a parameter can only be given a value once. ## How the display resolves a collision A dict display is evaluated left to right, and every `**` operand is inserted entry by entry, exactly like a fresh dict being filled by repeated `dict.update` calls. So the **last write wins on value**. What surprises people is the ordering: since Python 3.7 dict iteration order is insertion order, and re-writing an existing key does *not* move it. - `{**{'k': 1, 'j': 0}, **{'k': 2}}` is `{'k': 2, 'j': 0}` — the value came from the second mapping, the *position* came from the first. - Explicit entries interleave with unpackings under the same rule: `{**{'a': 1}, 'a': 2, **{'a': 3}}` is `{'a': 3}`. ## How the call resolves a collision — it does not In a call, each entry of a `**` operand becomes a keyword argument of that name. Two operands carrying the same key means the same parameter is named twice, and CPython raises `TypeError: f() got multiple values for keyword argument 'x'`. Crucially this is a *runtime* error, raised while the call is being assembled, before the function body runs, and it depends on the data — a merge that has run cleanly for months blows up the first time two mappings overlap. The identical error covers the mixed form `f(x=1, **{'x': 2})`, and a near-identical one covers the classic `f(1, x=2)` where a positional argument has already filled `x`. ## Compile time versus run time Writing the duplicate out literally is a *different* error in a different phase: `f(x=1, x=2)` never reaches the interpreter's call machinery, because the compiler rejects it with `SyntaxError: keyword argument repeated: x`. In an interview it is worth naming both: - a static duplicate is a SyntaxError at compile time, - a dynamic duplicate arriving through `**` is a TypeError at call time. That is also why the dynamic form is the dangerous one — no linter run and no import of the module will surface it. ## The fix, and why it changes the semantics If the caller genuinely wants "later layer wins", merge before unpacking: `f(**{**defaults, **overrides})`. The collision is now resolved by the display's last-write-wins rule, and only one `**` reaches the call, so no parameter is bound twice. That is a deliberate policy decision, not a workaround: you have chosen that `overrides` outranks `defaults`. If instead you want the *first* mapping to win, swap the operands, or reach for `collections.ChainMap`, whose lookup rule is the opposite — the **first** mapping in the chain wins, and it is a lazy view rather than a copy. ## Two limits of the merge worth stating 1. First, it is **shallow**. `{**{'limits': {'batch': 10}}, **{'limits': {'batch': 50}}}` replaces the whole nested dict; it does not merge `batch` inside it. Anything wanting a recursive merge has to write one. 2. Second, the values in the result are the *same objects* as in the sources, so mutating a nested list reached through the merged dict is visible through the original. ## Key types differ between the two forms - The display accepts any hashable key: `{**{1: 'a'}, **{(2, 3): 'b'}}` works fine. - The call form cannot, because keyword names must be strings — `f(**{1: 'a'})` raises `TypeError: keywords must be strings`. A mapping that came from parsed input or a database row is a common source of that surprise; validate the keys before unpacking a foreign mapping into a call. ## A note on cost The display builds a fresh dict sized to the union of its operands every time it is evaluated. Assembling one snapshot at startup is nothing; doing it per record on a hot path is a real allocation, and `collections.ChainMap` — which copies nothing and searches its layers on lookup — is the cheaper shape when the layers are large or rebuilt often. The tradeoff is that a ChainMap stays coupled to the mappings behind it, where a merged dict is a snapshot taken at one moment. ## The whole thing in one sentence A display merges and the last value wins; a call binds and a second binding is an error. Once that distinction is clear, both the silent overwrite and the TypeError stop being surprises and become the two halves of one rule.
- Where does a key that appears in both mappings sit in the merged dict's iteration order?At the position of its **first** insertion, carrying the **last** value. `{**{'k': 1, 'j': 0}, **{'k': 2}}` is `{'k': 2, 'j': 0}`: re-writing an existing key updates the value without moving the entry. Dicts have preserved insertion order as a language guarantee since Python 3.7.
- Is `f(x=1, x=2)` the same error as `f(**d1, **d2)` with a shared key?No, and the phase differs. The literal repeat is rejected by the compiler as `SyntaxError: keyword argument repeated`, so the module never imports. The duplicate arriving through `**` is a `TypeError: got multiple values for keyword argument` raised at call time, and it depends entirely on the runtime data — which is why it can lie dormant for a long while.
- Does the dict display merge nested mappings recursively?No, it is shallow. A key present in both mappings has its whole value replaced, so a nested dict from the later mapping wins outright rather than being merged key by key. Recursive merging has to be written by hand, and the values in the result are the same objects as in the sources, not copies.
saying these in an interview costs you the question
- Says the first value wins in a {**a, **b} merge
- Expects a TypeError from a duplicate key in a dict display
- Thinks the surviving key moves to the end of the merged dict
- Believes {**a, **b} merges nested dictionaries recursively
- Cannot separate the compile-time repeated keyword from the runtime TypeError
- Unpacks a foreign mapping into a call without checking its keys are strings