# The Config Blender

> Config collision. The surviving values after a merge.

Canonical URL: <https://datadriven.io/problems/the_config_blender>

Domain: Python · Difficulty: easy · Seniority: L3 · Asked in: Cloudflare

## Problem

Configuration arrives in layers: a `base` config and an `override` that should win wherever the two collide. Merge them into one dict where any key in `override` replaces the matching `base` value, except when both sides hold a nested dict at that key, where the same rule plays out one level deeper, all the way down. Keys present only in `base` carry through untouched, and neither input may be modified.

## Example 1

Input:

```
base = {"a":1,"b":{"x":10,"y":20}}, override = {"b":{"y":30,"z":40},"c":3}
```

Output:

```
{"a":1,"b":{"x":10,"y":30,"z":40},"c":3}
```

## Example 2

Input:

```
base = {"db":{"host":"localhost","port":5432}}, override = {"db":{"host":"prod.db.com"}}
```

Output:

```
{"db":{"host":"prod.db.com","port":5432}}
```

## Worked solution and explanation

### What this really is

Strip the config-management costume off and this is a single recursive overwrite with exactly one interesting branch. For every key in the override you make one decision: descend, or replace. You descend ONLY when both sides hold a dict at that key; in every other collision the override value wins outright. Candidates who reach for `base.update(override)` look finished in five seconds and are wrong the instant a nested dict appears, because update swaps the whole nested dict instead of blending it. The thing that actually separates people is the mixed-type collision: a dict on one side, a scalar on the other. Forget to guard BOTH isinstance checks and you either crash trying to merge an int or silently keep the wrong value.

---

### How to get there

#### Step 1: Walk the override's keys, not the base's

The override is the authority on what survives, so walk its keys. Base-only keys need no decision; they just ride along in the copy you started from.

#### Step 2: Guard the recursion with two isinstance checks

Descend ONLY when the key exists in both and both values are dicts. Any other shape (scalar vs scalar, dict vs scalar, scalar vs dict) is a flat replace where the override value wins. Both isinstance checks must pass before you recurse, or the mixed-type case bites.

#### Step 3: Copy first so you never mutate the input

Start from `dict(base)` so keys absent from the override survive, and so you never write into the caller's original dict. The recursion produces fresh dicts at every level it touches.

---

### The solution

**Recursive deep merge with override precedence**

```python
def deep_merge(base: dict, override: dict) -> dict:
    merged = dict(base)
    for key, value in override.items():
        if key in merged and isinstance(merged[key], dict) and isinstance(value, dict):
            merged[key] = deep_merge(merged[key], value)
        else:
            merged[key] = value
    return merged
```

*One branch does all the work: both-are-dicts recurses, everything else replaces.*

**Naive: base.update(override)**

base={'db': {'host': 'local', 'port': 5432}} updated with {'db': {'host': 'prod'}} gives {'db': {'host': 'prod'}}. The whole nested dict is replaced and `port` is gone.

**Correct: recursive merge**

The same inputs give {'db': {'host': 'prod', 'port': 5432}}. The nested dicts are blended, so `port` survives while `host` is overridden.

> **Trick to solving**
>
> The single insight: recurse only when BOTH sides are dicts at the same key. Everything else is a plain replace. Encode that as one if-condition and the whole problem collapses.

> **Interviewers watch for**
>
> Whether you create a new dict instead of mutating the base. Mutating the input is a common production bug that causes side effects in callers who still reference the original config.

> **Common pitfall**
>
> Using `dict.update()` at the top level, or recursing on a key where only one side is a dict. The first clobbers nested dicts entirely; the second crashes on the dict-vs-scalar collision.

> **Performance insight**
>
> Time is O(n) over the total number of keys across all nesting levels of both dicts. Space is O(n) for the merged output, and recursion depth equals the maximum nesting depth.

---

## Common follow-up questions

- What if the nesting depth could be very large? _(Tests awareness of Python's recursion limit and iterative alternatives using an explicit stack.)_
- How would you handle list values? _(Tests design decision: concatenate, replace, or merge by index?)_
- What if you need a three-way merge (base, env, local)? _(Tests chaining: `deep_merge`(`deep_merge`(base, env), local).)_

## Related

- [All practice problems](https://datadriven.io/problems)
- [Mock interview mode](https://datadriven.io/interview/the_config_blender)
- [Python Interview Questions](https://datadriven.io/python-interview-questions)
- [Data Engineering Interview Prep Guide](https://datadriven.io/data-engineer-interview-prep)
- [Daily Challenge](https://datadriven.io/daily)

---

Source: DataDriven (https://datadriven.io). DataDriven is the data engineering interview community. Live code execution in SQL, Python, and Spark sandboxes. Every feature is open to every member.