# The Lazy Unpacker

> Peel back every layer until only the values remain.

Canonical URL: <https://datadriven.io/problems/the_lazy_unpacker>

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.

## 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**

```python
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))
```

> **Why it stays cheap**
>
> Time is O(n) in the number of leaves: each value is touched exactly once. Space is O(d) where d is the maximum nesting depth, the height of the generator chain on the call stack. No intermediate lists are built during the walk; only the final `list()` allocates, and it holds exactly the leaves.

> **Interviewers watch for**
>
> Whether you reach for `yield from` instead of manually re-yielding the recursive result, and whether you keep everything lazy until a single `list()` at the boundary. Both signal that you think in terms of streams, not accumulated buffers.

**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.

> **The one that bites**
>
> The most common wrong answer is `yield _gen(item)`. It type-checks, it runs, and it produces a list of the right length one level up, which is exactly why people miss it under time pressure. Trace one nested input by hand before you trust it.

---

## 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.)_

## Related

- [All practice problems](https://datadriven.io/problems)
- [Mock interview mode](https://datadriven.io/interview/the_lazy_unpacker)
- [Python Interview Questions](https://datadriven.io/python-interview-questions)
- [Data Engineering Interview Prep Guide](https://datadriven.io/data-engineer-interview-prep)
- [Daily Challenge](https://datadriven.io/daily)

---

Source: DataDriven (https://datadriven.io). DataDriven is the data engineering interview community. Live code execution in SQL, Python, and Spark sandboxes. Every feature is open to every member.