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

Recursive generator with yield from
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