Dictionaries as Tables
Merge Two Dicts
Combine a defaults dict and an overrides dict so that override values win on
shared keys. The trace shows merged filling with all defaults first, then
the second loop overwriting port and debug with the override values.
By hand
Copy every entry from defaults into merged, then iterate over overrides
and write each entry into merged — shared keys are silently overwritten,
giving overrides priority.
naive.py
Replay: real traced execution (multi-file project)
defaults = {'host': 'localhost', 'port': 80, 'debug': False}
overrides = {'port': 443, 'debug': True}
merged = {}
for k, v in defaults.items():
merged[k] = v
for k, v in overrides.items():
merged[k] = v
print('RESULT:', {k: merged[k] for k in sorted(merged)})
defaults ← {'host': 'localhost', 'port': 80, 'debug': False}
1defaults = {'host': 'localhost', 'port': 80, 'debug': False}2overrides = {'port': 443, 'debug': True}values this step{'host': 'localhost', 'port': 80, 'debug': False}defaultsoverrides ← {'port': 443, 'debug': True}
1defaults = {'host': 'localhost', 'port': 80, 'debug': False}2overrides = {'port': 443, 'debug': True}3merged = {}values this step{'port': 443, 'debug': True}overridesmerged ← {}
2overrides = {'port': 443, 'debug': True}3merged = {}4for k, v in defaults.items():values this step{}mergedk ← 'host', v ← 'localhost'
3merged = {}4for k, v in defaults.items():5 merged[k] = vvalues this step'host'k'localhost'vmerged ← {'host': 'localhost'}
4for k, v in defaults.items():5 merged[k] = v6for k, v in overrides.items():values this step{} → {'host': 'localhost'}mergedk ← 'port', v ← 80
3merged = {}4for k, v in defaults.items():5 merged[k] = vvalues this step'host' → 'port'k'localhost' → 80vmerged ← {'host': 'localhost', 'port': 80}
4for k, v in defaults.items():5 merged[k] = v6for k, v in overrides.items():values this step{'host': 'localhost'} → {'host': 'localhost', 'port': 80}mergedk ← 'debug', v ← False
3merged = {}4for k, v in defaults.items():5 merged[k] = vvalues this step'port' → 'debug'k80 → Falsevmerged ← {'host': 'localhost', 'port': 80, 'debug': False}
4for k, v in defaults.items():5 merged[k] = v6for k, v in overrides.items():values this step{'host': 'localhost', 'port': 80} → {'host': 'localhost', 'port': 80, 'debug': False}mergedfor k, v in defaults.items():
3merged = {}4for k, v in defaults.items():5 merged[k] = vk ← 'port', v ← 443
5 merged[k] = v6for k, v in overrides.items():7 merged[k] = vvalues this step'debug' → 'port'kFalse → 443vmerged ← {'host': 'localhost', 'port': 443, 'debug': False}
6for k, v in overrides.items():7 merged[k] = v8print('RESULT:', {k: merged[k] for k in sorted(merged)})values this step{'host': 'localhost', 'port': 80, 'debug': False} → {'host': 'localhost', 'port': 443, 'debug': False}mergedk ← 'debug', v ← True
5 merged[k] = v6for k, v in overrides.items():7 merged[k] = vvalues this step'port' → 'debug'k443 → Truevmerged ← {'host': 'localhost', 'port': 443, 'debug': True}
6for k, v in overrides.items():7 merged[k] = v8print('RESULT:', {k: merged[k] for k in sorted(merged)})values this step{'host': 'localhost', 'port': 443, 'debug': False} → {'host': 'localhost', 'port': 443, 'debug': True}mergedfor k, v in overrides.items():
5 merged[k] = v6for k, v in overrides.items():7 merged[k] = vstdout ← RESULT: {'debug': True, 'host': 'localhost', 'port': 443}
7 merged[k] = v8print('RESULT:', {k: merged[k] for k in sorted(merged)})values this stepRESULT: {'debug': True, 'host': 'localhost', 'port': 443}stdout
The Pythonic way
{**defaults, **overrides} unpacks both dicts into a single literal in one
expression. Later entries win on duplicate keys, so placing overrides
second gives it priority — the same rule as the two-loop version.
library.py
defaults = {'host': 'localhost', 'port': 80, 'debug': False}
overrides = {'port': 443, 'debug': True}
merged = {**defaults, **overrides}
print('RESULT:', {k: merged[k] for k in sorted(merged)})
RESULT: {'debug': True, 'host': 'localhost', 'port': 443}
Implementation notes
- Python 3.9+ adds the union operator:
defaults | overridesproduces the same result as{**defaults, **overrides}and reads more clearly as a merge. - Argument order matters:
{**overrides, **defaults}would give defaults priority instead; the rightmost dict wins on any duplicate key. - Neither approach mutates the input dicts. If in-place update is acceptable,
defaults.update(overrides)modifiesdefaultsdirectly without creating a new dict.