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)})
  1. 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}defaults
  2. overrides ← {'port': 443, 'debug': True}

    1defaults = {'host': 'localhost', 'port': 80, 'debug': False}2overrides = {'port': 443, 'debug': True}3merged = {}
    values this step{'port': 443, 'debug': True}overrides
  3. merged ← {}

    2overrides = {'port': 443, 'debug': True}3merged = {}4for k, v in defaults.items():
    values this step{}merged
  4. k ← 'host', v ← 'localhost'

    3merged = {}4for k, v in defaults.items():5    merged[k] = v
    values this step'host'k'localhost'v
  5. merged ← {'host': 'localhost'}

    4for k, v in defaults.items():5    merged[k] = v6for k, v in overrides.items():
    values this step{} {'host': 'localhost'}merged
  6. k ← 'port', v ← 80

    3merged = {}4for k, v in defaults.items():5    merged[k] = v
    values this step'host' 'port'k'localhost' 80v
  7. merged ← {'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}merged
  8. k ← 'debug', v ← False

    3merged = {}4for k, v in defaults.items():5    merged[k] = v
    values this step'port' 'debug'k80 Falsev
  9. merged ← {'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}merged
  10. for k, v in defaults.items():

    3merged = {}4for k, v in defaults.items():5    merged[k] = v
  11. k ← 'port', v ← 443

    5    merged[k] = v6for k, v in overrides.items():7    merged[k] = v
    values this step'debug' 'port'kFalse 443v
  12. merged ← {'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}merged
  13. k ← 'debug', v ← True

    5    merged[k] = v6for k, v in overrides.items():7    merged[k] = v
    values this step'port' 'debug'k443 Truev
  14. merged ← {'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}merged
  15. for k, v in overrides.items():

    5    merged[k] = v6for k, v in overrides.items():7    merged[k] = v
  16. stdout ← 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 | overrides produces 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) modifies defaults directly without creating a new dict.