# The Fallback Layer

> When the live values come back empty, something has to stand in.

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

Domain: Python · Difficulty: easy · Seniority: L3

## Problem

A service builds its runtime config by layering environment values over a baseline. Start from `primary`, the values read from the environment, and keep every key it has; whenever one of those values is `None`, fall back to the same key in `defaults`. A key that lives only in `defaults` never makes it into the result, and a `None` with no matching default stays `None`.

## Worked solution and explanation

### What this is really testing

Strip the config-loader costume and this is a per-key null-coalesce, not a dictionary merge. The keys that survive are decided entirely by `primary`; `defaults` is only a bench of replacement values you reach for when `primary` handed you a `None`. Almost everyone's hand reaches for `{**defaults, **primary}` on reflex, and that is the trap: it drags in every key that lived only in `defaults`, and it happily keeps the `None` values you were supposed to fill. Get that wrong and your service boots with settings the environment never set.

> **The realization**
>
> You are not merging two dicts. You are iterating one of them, `primary`, and asking a single question per key: is this value `None`, and does `defaults` have something to offer for it? Treat `defaults` as a lookup table, never as a source of keys.

---

### Walking through it

#### Step 1: Iterate primary, not defaults

The output's key set is exactly `primary`'s key set, so loop over `primary.items()` and let that drive what lands in the result. Nothing in `defaults` gets to introduce a new key.

#### Step 2: Fall back only on a real None

For each value, keep it as-is unless it is exactly `None` and `defaults` carries the same key. Use `is None`, not a truthiness check, or you will treat `0`, `''`, and `False` as holes and overwrite perfectly good values. A `None` with no matching default simply stays `None`.

---

### The solution

**Per-key coalesce driven by primary**

```python
def coalesce(primary, defaults):
    result = {}
    for key, value in primary.items():
        if value is None and key in defaults:
            result[key] = defaults[key]
        else:
            result[key] = value
    return result
```

**The reflex: dict-unpack merge**

`{**defaults, **primary}` returns the union of both key sets and preserves every `None` in `primary`. Wrong key set, wrong values.

**Per-key coalesce**

Iterate `primary`, swap in a default only where the value is `None` and the key exists in `defaults`. Right key set, holes filled.

> **Common pitfall**
>
> Two reflexes sink this. First, `{**defaults, **primary}`: it looks right but leaks defaults-only keys and never touches the `None` values. Second, `if value:` in place of `if value is None:`, which silently swallows `0`, empty strings, and `False` as if they were missing.

> **Interviewers watch for**
>
> The exact tell is whether you separate 'absent key' from 'present but None'. A candidate who writes `is None` and can explain why `0` must survive is reasoning about the data, not pattern-matching on syntax.

> **Performance insight**
>
> One pass over `primary` with O(1) membership checks against `defaults`: O(p) time and O(p) space, where p is the number of primary keys. The `defaults` dict is never iterated, only probed.

---

## Common follow-up questions

- What if the merge should be recursive for nested dicts? _(Tests deep merge patterns where nested dicts at the same key should be merged rather than replaced.)_
- How would you handle a chain of three or more config layers? _(Tests whether you can generalize to N-layer merging by folding left to right.)_
- What if None values in primary should explicitly override defaults to None? _(Tests understanding of different null semantics: coalesce vs. explicit null override.)_

## Related

- [All practice problems](https://datadriven.io/problems)
- [Mock interview mode](https://datadriven.io/interview/the_fallback_layer)
- [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.