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
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 mergedCommon follow-up questions
- What if list values should be concatenated rather than overwritten? (Tests adding an isinstance(value, list) branch with concatenation instead of replacement.)
- How would you make this iterative for deeply nested configs? (Tests an explicit stack of (`base_dict`, `override_dict`) frames to avoid Python's recursion limit on very deep config.)
- How would you layer three configs (defaults, environment, local) so the last one wins? (Tests `recursive_merge`(`recursive_merge`(base, env), local) and whether precedence stays correct.)