To flatten a list of lists in Python, three methods actually win: list(itertools.chain.from_iterable(nested)) is the fastest, [x for sub in nested for x in sub] is the most readable, and a for loop with .extend() reads clearest in production code. Skip sum(nested, []) for flattening; it's quadratic and turns into a real performance problem as the input grows. For arbitrarily nested structures (lists inside lists inside lists), no one-liner works correctly; you need a small recursive or stack-based helper. And watch the string trap: strings are iterable, so a naive flatten happily turns "abc" into 'a', 'b', 'c'. This piece is one of 17 short explainers in our Python Concepts Explained reference.
Key Takeaways
Best general purpose:list(itertools.chain.from_iterable(nested)). Fastest, written in C, ships with the standard library.
Most readable:[x for sub in nested for x in sub]. Two-loop comprehension that everybody recognizes.
Production loop:for sub in nested: out.extend(sub). Slightly slower than chain, but the most obvious one to read at 2 AM.
String trap: strings are iterable. chain.from_iterable(["abc", "de"]) yields 'a', 'b', 'c', 'd', 'e'. Filter your input or check isinstance(item, list).
Arbitrary depth: no one-liner works. Write a recursive helper for moderately deep structures, or an iterative stack-based version for very deep ones to avoid Python's recursion limit.
Three real cases: the happy path, the string trap, and the depth limit. Each one wants a different method.
The Three Methods That Actually Win
For a list of lists that's exactly one level deep, three methods cover every situation worth covering.
chain.from_iterable takes one iterable of iterables and yields each inner element in order. It's written in C inside CPython, which is why it consistently wins benchmarks on this task.
2. Nested list comprehension (most readable)
nested = [[1, 2], [3, 4], [5, 6]]
flat = [x for sub in nested for x in sub]
# [1, 2, 3, 4, 5, 6]
Read it left-to-right: "for each sub in nested, for each x in sub, take x." Pythonistas recognize the shape instantly. Slightly slower than chain.from_iterable on large inputs; for almost any real-world list, you won't notice.
3. for loop with .extend() (clearest in production)
nested = [[1, 2], [3, 4], [5, 6]]
flat = []
for sub in nested:
flat.extend(sub)
# [1, 2, 3, 4, 5, 6]
Three lines that read like English. When your colleague debugs the function at 2 AM, this is the version they want to see.
What about sum(nested, [])?
You'll see this in old answers:
# Don't do this
flat = sum(nested, [])
It works, and it's almost a one-liner, so it spreads. But sum repeatedly concatenates lists, which is O(n²) in the number of sublists. At 10 sublists you can't measure the difference; at 10,000 sublists it's seconds slower than the alternatives. Skip it.
Watch Out — Strings Are Iterable
This trap doesn't appear in any of the standard "X ways to flatten" tutorials, but it bites everyone eventually. Strings are iterable in Python. A naive flatten that walks any iterable will happily break a string into its characters:
If you want strings preserved as single items, you have to special-case them. The cleanest pattern uses a loop with an explicit type check:
def flatten_one_level(nested, preserve=(str, bytes)):
out = []
for item in nested:
if isinstance(item, list) and not isinstance(item, preserve):
out.extend(item)
else:
out.append(item)
return out
flatten_one_level([[1, 2], "abc", [3]])
# [1, 2, 'abc', 3]
The same trap applies to bytes and to any other iterable you don't want unpacked (a custom class with __iter__, a pathlib.Path in some Python versions, and so on). Whitelist what counts as "nested" rather than blacklisting strings; you'll catch more cases.
Rule: any flatten function intended for nested lists should treat str and bytes as opaque, not as iterables. Building the special case in by default is cheaper than debugging it later.
Arbitrarily Nested Lists
The one-liners only collapse one level. chain.from_iterable([1, [2, [3, [4]]]]) fails outright (the integer 1 isn't iterable), and even if it didn't, you'd get [1, 2, [3, [4]]] with only one layer peeled. For arbitrary depth, you need recursion or an explicit stack.
Recursive flatten
def deep_flatten(nested):
for item in nested:
if isinstance(item, list):
yield from deep_flatten(item)
else:
yield item
list(deep_flatten([1, [2, [3, [4]]]]))
# [1, 2, 3, 4]
The generator form is concise and lazy: it produces values on demand, so partial consumption doesn't pay for the whole walk. yield from handles the recursion bookkeeping for you.
Iterative (stack-based) flatten
For structures that could be deeper than CPython's default recursion limit (1000 frames), use a stack:
def deep_flatten_iter(nested):
out = []
stack = [iter(nested)]
while stack:
try:
item = next(stack[-1])
except StopIteration:
stack.pop()
continue
if isinstance(item, list):
stack.append(iter(item))
else:
out.append(item)
return out
This version handles tens of thousands of layers without raising RecursionError. The trade-off is more code; you'd reach for it only when you actually expect deep input.
Performance — chain.from_iterable Wins
Concrete timings on a list of 1,000 sublists, each with 10 integers (CPython 3.12, single thread):
Method
Time per call
Relative
list(chain.from_iterable(nested))
~85 µs
1.0× (baseline)
[x for s in nested for x in s]
~120 µs
1.4×
for sub in nested: out.extend(sub)
~130 µs
1.5×
functools.reduce(operator.iconcat, nested, [])
~125 µs
1.5×
np.array(nested).flatten().tolist()
~250 µs
3× (plus NumPy import)
sum(nested, [])
~8 ms
~95× SLOWER
The three winners are within ~50% of each other for this workload. The clear loser is sum; the clear "only if you're already using NumPy" choice is np.array. Microseconds rarely matter, but the sum trap matters at scale — a script that flattens millions of small lists will spend minutes in sum and milliseconds in chain.
Decision Tree — Which Method, When
Your input
Use this
One-level deep, all lists
list(chain.from_iterable(nested)) (or list comp)
Mixed lists + strings or other iterables you want preserved
Custom loop with isinstance(item, list) check
Arbitrarily nested, moderate depth (< few hundred)
Recursive generator with yield from
Arbitrarily nested, very deep or unknown depth
Iterative stack-based version
Numeric matrix, regularly shaped
np.array(nested).flatten().tolist() (only if you already use NumPy)
DataFrame column of lists
df["col"].explode() (see below)
Bonus: Pandas .explode()
If your data already lives in a pandas DataFrame and one column holds lists, you don't need to flatten manually. Series.explode() converts each list-cell into its own row:
import pandas as pd
df = pd.DataFrame({
"user": ["alice", "bob"],
"tags": [["python", "ml"], ["go"]],
})
df.explode("tags")
# user tags
# 0 alice python
# 0 alice ml
# 1 bob go
The index repeats so each new row remembers which original row it came from. This is the "flatten" most data-science workflows actually want; nothing else in this article matches that shape (pandas docs).
Common Mistakes
Four traps to watch for:Mistake 1: using sum(nested, []). It's tempting because it's almost a one-liner. It's quadratic; benchmarks show it lagging the alternatives by orders of magnitude on large inputs.Mistake 2: applying a one-liner to deeply nested data.chain.from_iterable and the list comprehension only peel one layer. Verify the depth of your input before committing.Mistake 3: forgetting strings are iterable. Any flatten that walks every iterable will split "abc" into three characters. Default to a custom loop that whitelists list.Mistake 4: reaching for NumPy on irregular data.np.array([[1, 2], [3]]) creates a one-dimensional object array, not a 2D matrix, and .flatten() on that returns the original sublists instead of the elements. NumPy expects regular shape.
Frequently Asked Questions
What is the fastest way to flatten a list of lists in Python?
Use list(itertools.chain.from_iterable(nested)). It's roughly 30 to 50 percent faster than a list comprehension on typical inputs and is written in optimized C code. For a one-level deep flatten, no pure-Python method beats it. Avoid sum(nested, []) for flattening: it's O(n²) and is dramatically slower as the input grows.
Why does flattening a list of strings produce individual characters?
Strings are iterable in Python, so any flatten function that walks every iterable will treat "abc" as the characters 'a', 'b', 'c'. To keep strings intact, filter the input or check isinstance(item, list) inside your loop. The right rule: a flatten function intended for nested lists should special-case str (and bytes) as non-iterable for flattening purposes.
How do I flatten an arbitrarily nested list?
Use a recursive helper that walks each element and, if it's a list (or any iterable you choose), recurses into it. Otherwise yield the element as-is. For very deep structures that risk Python's recursion limit (default 1000), use the iterative stack version instead. No one-liner handles arbitrary depth correctly.
Should I use NumPy to flatten lists?
Only if your data is already numeric and regularly shaped. np.array(nested).flatten().tolist() is very fast for numeric matrices but breaks on irregular sublist lengths and adds a NumPy dependency. For mixed-type or jagged data, stick with itertools.chain.from_iterable() or a list comprehension.
The Bottom Line: Pick by Depth, Type, and Use Case
For a one-level list of lists, list(chain.from_iterable(nested)) is the answer; for readability, a nested list comprehension matches it without losing much speed. For mixed types or strings, build the special case in. For arbitrary depth, write a small recursive helper (or an iterative stack for very deep input). And if you live in pandas, explode() is the flatten you actually wanted. With those four choices in mind, every flatten question is a 30-second decision. For the rest of the most-asked Python concept questions, browse the full Python Concepts Explained index.
Drill List Idioms Until They're Reflex
CodeGym's Python track turns list and itertools idioms into muscle memory through 800+ hands-on tasks across 62 levels. The AI validator checks every submission in seconds; the AI mentor explains what broke when you get stuck. First level free; full plan on the pricing page.
Learn Python on the free track →
GO TO FULL VERSION