SnippetPython

Flatten a nested list

Flattens a list of lists by one level using a nested list comprehension, with a note on when you actually need recursion instead.

Last updated

Snippetpython
def flatten(nested):
    return [item for sub in nested for item in sub]

# flatten([[1, 2], [3], [4, 5]]) -> [1, 2, 3, 4, 5]

How it works

This is a nested comprehension read left to right the same way you'd write the equivalent loop: for sub in nested, then for item in sub, appending each item — it's the idiomatic one-liner for flattening exactly one level, and it's faster than sum(nested, []) which builds and discards an intermediate list at every step.

Keeping it to one level is a deliberate scope decision: a function that flattens arbitrarily deep, mixed-depth structures needs recursion and a type check to tell a list apart from a leaf value, which is a different (and slower, less predictable) tool than this one.

Edge cases to know

  • →Nested lists deeper than one level stay nested: flatten([[1, [2, 3]], [4]]) returns [1, [2, 3], 4], not a fully flat list — reach for a recursive flattener if your input's depth isn't fixed.
  • →Every element of nested must itself be iterable; flatten([1, [2, 3]]) raises a TypeError on the bare 1, since int isn't iterable.
  • →Order is preserved and duplicates are kept exactly as they appear — this does no deduplication or sorting.

Part by part

for sub in nested for item in sub
Nested comprehension clauses, read left to right exactly as nested for loops would be written. The order is the part people reverse.
[item
The output expression comes first in a comprehension, before the loops that produce it — the opposite of the execution order.

Common mistakes

  • [item for item in sub for sub in nested]

    The clauses reversed. Python evaluates them left to right, so sub is referenced before it is bound and this raises NameError — or worse, silently uses a leftover sub from an enclosing scope.

    [item for sub in nested for item in sub]
  • sum(nested, [])

    Works and is quadratic: it builds a new list on every addition, so flattening a thousand sublists copies far more than it needs to. Fine for a handful, wrong at any real size.

  • Expecting it to flatten deeply

    This removes exactly one level. A list containing lists of lists comes back still containing lists, which is easy to miss when the data is mostly two levels deep and occasionally three.

Questions

How do I flatten arbitrarily deep nesting?

Recursion is the usual answer: yield from a recursive call when an item is itself iterable. Be careful to exclude strings, which are iterable and would be flattened into individual characters.

Is there a standard library version?

itertools.chain.from_iterable does the same job, lazily and slightly faster on large inputs. It is the better choice inside a pipeline where you do not need a list immediately.

What if some elements are not lists?

It raises TypeError on anything not iterable, and quietly splits a string into characters because strings are. Both cases argue for validating the shape before flattening.

Why is the clause order so confusing?

Because the output expression is written first and the loops after, in the order you would nest them. Reading everything after the opening bracket as ordinary nested for loops makes it consistent.

Related in Snippets