The Lazy Unpacker
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
- L4
- Asked in
- Foursquare
Problem
A nightly job emits grouped metric readings as a list nested whose entries are either numbers or further lists, nested to arbitrary depth. Collapse it into a single flat list of every non-list value, preserving the left-to-right order in which they appear. An empty nested, or one holding only empty lists, comes back as an empty list.
Peel back every layer until only the values remain.
Example 1
Input:
nested = [[1,[2,3]],[4,5]]
Output:
[1,2,3,4,5]
Example 2
Input:
nested = [[1,2,3]]
Output:
[1,2,3]
Worked solution and explanation
What this really is
Strip the data-cleaning costume and this is a depth-first walk of a tree, collecting the leaves. Anyone can tell that a number is a leaf and a list is a branch to descend into. What actually separates candidates is delegating the recursion cleanly, and the whole problem lives in the one-character gap between yield and yield from. Write yield _gen(item) and you hand back generator objects buried inside your output instead of their values. At a glance the structure looks right, and then it fails every single test.
Walking the tree
Step 1: Classify each element as branch or leaf
Loop the items and ask one question per element: is it a list? isinstance(item, list) answers it. A list is a branch to recurse into; anything else is a leaf you keep. That binary decision is the entire control flow.
Step 2: Delegate the recursion
On a branch, yield from _gen(item) streams every leaf the recursive call produces, in order, without your code touching them. The alternative, looping the recursive result and re-yielding each value, works but is noise; yield from is the idiom an interviewer expects you to reach for here.
Step 3: Materialize once, at the top
The generator stays lazy the whole way down. The single entrypoint flatten_generator is the only place that materializes, wrapping the inner generator in list() so the graded return value is a flat list rather than a generator object. Keeping the helper nested means one clean top-level function.
The solution
def flatten_generator(nested: list) -> list:
def _gen(items):
for item in items:
if isinstance(item, list):
yield from _gen(item)
else:
yield item
return list(_gen(nested))- yield _gen(item)
- Yields the generator OBJECT itself as a single element. Your output ends up holding generators instead of numbers, so [[1,2]] flattens to [<generator>] and every assertion fails.
- yield from _gen(item)
- Delegates to the sub-generator and yields each of ITS values in turn. [[1,2]] flattens to [1, 2], which is what the tests expect.
Common follow-up questions
- What exactly is the difference between yield and yield from here? (Tests that yield from delegates to a sub-generator while yield produces one value.)
- How would you stop flattening past a given depth? (Tests adding a depth parameter that decrements on each recursive call and stops descending at zero.)
- What happens if the nesting is thousands of levels deep, and how would you handle it? (Tests awareness of Python's recursion limit and converting to an explicit stack-based walk.)