Roots and Branches
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
- L5
- Asked in
- Airbnb
Problem
A storage indexer hands you object keys as slash-separated paths, and you need them folded into a nested dict that mirrors the directory layout: every directory segment maps to an inner dict and every file (the final segment) maps to None. Paths that share a leading directory must collapse onto the same node instead of clobbering each other, and an empty list yields an empty dict.
Flat strings carry a hierarchy. Recover it.
Example 1
Input:
paths = ["src/main.py","src/utils/helper.py","README.md","src/utils/config.py"]
Output:
{"src":{"utils":{"config.py":null,"helper.py":null},"main.py":null},"README.md":null}Example 2
Input:
paths = ["a/b/c/d.txt"]
Output:
{"a":{"b":{"c":{"d.txt":null}}}}Worked solution and explanation
What this really is
Under the file-system costume this is a trie insert: you thread each path into a shared prefix structure one segment at a time. Anyone can split on the slash and nest dicts. The thing that separates candidates is prefix sharing: when the second path under src/ shows up, do you reuse the src node you already created, or do you replace it with a fresh {} and silently lose the first file? Get that wrong and src/main.py vanishes the moment src/utils/helper.py is inserted.
Walking it
Step 1: Separate the directories from the file
'src/utils/helper.py'.split('/') gives ['src', 'utils', 'helper.py']. Peel the last element off as the filename and treat everything before it as the directory chain: *directories, filename = parts.
Step 2: Descend, reusing existing nodes
Start at the root dict and descend one directory at a time, calling setdefault at each level. The key move is that descent always lands on the node that is already there when one exists, so files inserted by earlier paths stay put.
Step 3: Mark the leaf file as None
Once you are standing on the deepest directory node, write the filename with a value of None. That None is what distinguishes a file from an (empty) directory dict downstream.
The solution
def build_file_tree(paths: list[str]) -> dict:
tree = {}
for path in paths:
*directories, filename = path.split('/')
node = tree
for directory in directories:
node = node.setdefault(directory, {})
node[filename] = None
return tree- Manual existence check
- for directory in directories: if directory not in node: node[directory] = {} node = node[directory] Correct, but the create-then-descend is two statements and one stray reassignment turns it into the overwrite bug.
- setdefault
- for directory in directories: node = node.setdefault(directory, {}) Same compute, one line, and reuse-or-create is atomic so the bug cannot sneak in.
Common follow-up questions
- How would you flatten the tree back into the original list of paths? (Tests recursive DFS with path accumulation.)
- What if you need to count files per directory, including subdirectories? (Tests post-order traversal counting leaves.)
- How would you handle paths with trailing or leading slashes? (Tests stripping empty segments from the split result.)
- What if two paths conflict, where one name is a file and another path treats it as a directory? (Tests validation and conflict resolution.)