The Fallback Layer
An easy Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.
- 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.
When the live values come back empty, something has to stand in.
Example 1
Input:
primary = {"host":"db.prod","port":null,"timeout":30}, defaults = {"host":"localhost","port":5432,"timeout":10}Output:
{"host":"db.prod","port":5432,"timeout":30}Example 2
Input:
primary = {"a":null,"b":null}, defaults = {"a":1,"b":2}Output:
{"a":1,"b":2}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.
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
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 everyNoneinprimary. Wrong key set, wrong values.- Per-key coalesce
- Iterate
primary, swap in a default only where the value isNoneand the key exists indefaults. Right key set, holes filled.
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.)