The Shortest Path Home
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
- L3
- Asked in
- Amazon
Problem
API responses arrive as dicts nested arbitrarily deep, and the warehouse needs them as single-level rows. Collapse nested into a flat dict whose keys are the full path down to each leaf, joined by separator, where any value that is not itself a dict is a leaf. An empty inner dict contributes no key, so that branch simply disappears from the result.
Every leaf remembers the road it came from. Bring them all back on one line.
Example 1
Input:
nested = {"user":{"name":"Ada","address":{"zip":"75001","city":"Paris"}},"active":true}, separator = "_"Output:
{"user_name":"Ada","user_address_zip":"75001","user_address_city":"Paris","active":true}Example 2
Input:
nested = {"id":7,"meta":{}}, separator = "_"Output:
{"id":7}Worked solution and explanation
What this really is
This is recursion carrying a breadcrumb. The skill being probed: can you remember the path you walked down so far and stitch it onto each leaf, without dribbling a separator onto the front of every root key? Everyone writes the recursive descent. The candidates who stumble are the ones who concatenate prefix, separator, and key unconditionally and hand back keys like '_user_name' with a stray leading underscore on every top-level entry.
Break down the requirements
Step 1: Recurse with an accumulating prefix
Thread a prefix string through a recursive helper. When you descend into a sub dict, append the current key to the prefix using the separator. The recursion bottoms out the moment a value is not itself a dict: that value is a leaf and gets written under the full joined path.
Step 2: Respect the separator parameter
Default to '_' per the spec, but never hard code it inside the recursion. Threading the separator through the helper keeps the function reusable for dotted '.' notation or any custom delimiter, which is exactly the kind of reuse an interviewer probes with a follow-up.
Step 3: Handle the empty prefix correctly
On the first call there is no prefix yet, so the joined key is just the current key, not '_foo'. The guard f'{prefix}{separator}{key}' if prefix else key produces clean root keys and avoids leaking a leading separator into every output key.
The solution
def flatten_json(nested: dict, separator: str = '_') -> dict:
flat = {}
def _walk(obj, prefix):
for key, value in obj.items():
new_key = f'{prefix}{separator}{key}' if prefix else key
if isinstance(value, dict):
_walk(value, new_key)
else:
flat[new_key] = value
_walk(nested, '')
return flatCommon follow-up questions
- How would you also flatten lists by index, like `items_0_name`? (Extend the recursion to handle isinstance(value, list) and append the index to the prefix. Discuss whether the index should reuse the separator or switch to bracket notation.)
- How would you reverse the operation and unflatten? (Split each key on the separator, walk into a fresh dict creating sub dicts as needed, and assign the leaf. Talk through what happens when a key path collides with an existing leaf.)
- What if two paths collide after flattening? (For example {'`a_b`': 1, 'a': {'b': 2}} with '_' separator both produce `a_b`. Either raise on collision or pick a deterministic winner, but surface the choice rather than silently overwrite.)