The Lazy Stream

A hard Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.

Domain
Python
Difficulty
hard
Seniority
L5
Asked in
Foursquare

Problem

An upstream export arrives as a data list where some slots hold a lone value and others hold a short list of values that got batched together during ingestion. Return a flat list that splices each batched list's contents into its place, leaving everything else exactly as it sits (strings stay whole even though they iterate, and anything nested deeper than one level is left as-is).

Some slots hold one value, some hold a batch. Level them out.

Example 1

Input:

data = [1,[2,3],4,[5,6]]

Output:

[1,2,3,4,5,6]

Example 2

Input:

data = ["hello",["world"]]

Output:

["hello","world"]

Worked solution and explanation

One level, and one trap

This is one-level flattening wearing a generator costume. The real skill: knowing that yield from expands exactly one level, not recursively, and remembering that a str is iterable, so it needs an explicit guard. Anyone can loop and call isinstance. What separates candidates is branching on list specifically (not a generic iterable check) so 'abc' rides through whole, and resisting the urge to recurse into deeper nesting. Get the guard wrong and 'abc' comes back as 'a', 'b', 'c'; recurse too far and [1, [2, [3, 4]], 5] loses its inner [3, 4].


Break down the requirements

Step 1: Flatten exactly one level

If an element is a list, splice each of its items into place. Do not reach into the items themselves. That bright line is what keeps this from becoming a deep flatten: [1, [2, [3, 4]], 5] must come back as [1, 2, [3, 4], 5], with the inner [3, 4] intact.

Step 2: Treat strings as leaves

Even though str is iterable, a string stays intact. Check isinstance(item, list) rather than a generic iterable test, so 'hello' is yielded whole instead of exploding into 'h', 'e', 'l', 'l', 'o'. This is the single mistake that fails the most submissions on this shape.

Step 3: Use a generator and materialize at the boundary

Define an inner generator that does yield from item for lists and yields the item directly otherwise. Materialize with list(...) at the return so the public signature still returns a list, while the generator keeps auxiliary memory at O(1) if a downstream caller ever switches to streaming.


The solution

Generator with yield from for one level
def lazy_flatten(data: list) -> list:
    def _gen(items):
        for item in items:
            if isinstance(item, list):
                yield from item
            else:
                yield item
    return list(_gen(data))

Common follow-up questions