The Hostile Takeover

A medium Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.

Domain
Python
Difficulty
medium
Seniority
L4
Asked in
Circana

Problem

Pipeline configuration arrives in layers: a base dict of defaults and an override dict that customizes it per environment, both nestable to arbitrary depth. Combine them into one dict where override wins on any conflicting leaf, except that when a key holds a dict on both sides the two sub-dicts combine by that same rule rather than one replacing the other. Keys present in only one side carry through untouched, and neither input may be modified.

One dict eats another.

Example

Input:

base = {"a":1,"b":{"c":2,"d":3}}, override = {"b":{"d":99,"e":5},"f":6}

Output:

{"a":1,"b":{"c":2,"d":99,"e":5},"f":6}

Worked solution and explanation

What this really is

Strip the config-layering costume and this is a recursive deep merge with two traps packed into one branch condition. Anyone can loop over override and assign. What separates candidates: the recurse-only-when-BOTH-sides-are-dicts guard, and never touching base. Miss the first and {'cache': {'ttl': 60}} layered with {'cache': 0} either crashes or silently keeps stale nested data; miss the second and you corrupt the caller's defaults dict for every later environment that reuses it.


The trap

Step 1: Copy before you write

Build merged = dict(base) first, so the original defaults survive the call. A shallow copy is enough here: every nested dict you actually descend into gets rebuilt by the recursive call rather than edited in place, so the shared inner references are never written through.

Step 2: The guard that does the work

For each override key, recurse ONLY when the key already exists in merged AND both sides are dicts. That three-part condition is the entire problem. Drop any clause and a scalar-vs-dict case or a brand-new key takes the wrong branch.

Step 3: Otherwise, override wins

Every other case (a new key, a scalar value, a dict replacing a scalar) is a flat assignment of the override value. Keys that live only in base were never visited by the loop, so they pass through unchanged for free.


The solution

Immutable recursive deep merge
def recursive_merge(base, override):
    merged = dict(base)
    for key, value in override.items():
        if key in merged and isinstance(merged[key], dict) and isinstance(value, dict):
            merged[key] = recursive_merge(merged[key], value)
        else:
            merged[key] = value
    return merged

Common follow-up questions