How to Combine Iterables Using itertools.chain

The itertools.chain() function in Python provides an efficient way to treat multiple sequences as a single continuous iterable. Instead of concatenating data structures in memory using operators like + or unpacking syntax, itertools.chain() traverses each provided iterable sequentially and yields elements on demand. This article explains how itertools.chain() works under the hood, demonstrates its basic usage, highlights its memory benefits, and covers the alternative chain.from_iterable() method.

How itertools.chain() Works

The itertools.chain() function accepts zero or more iterables as arguments (such as lists, tuples, sets, or generators) and returns an iterator.

When iteration begins, chain() reads items from the first iterable until it is exhausted. It then automatically advances to the next iterable, continuing this process until all supplied iterables have been fully consumed.

Because it operates as an iterator, elements are evaluated lazily. It does not construct an intermediate collection holding all combined items, which avoids the memory overhead of duplicating large datasets.

Basic Syntax and Example

To use chain(), import it from the built-in itertools module and pass the iterables as positional arguments:

from itertools import chain

list_a = [1, 2, 3]
tuple_b = ('a', 'b', 'c')
set_c = {10, 20}

combined = chain(list_a, tuple_b, set_c)

for item in combined:
    print(item, end=' ')
# Output: 1 2 3 a b c 10 20

Memory Efficiency vs. Concatenation

Combining collections using list addition (list_a + list_b) or unpacking ([*list_a, *list_b]) allocates new memory to store the merged result. For large collections, this can cause significant memory spikes.

In contrast, itertools.chain() maintains references to the original iterables and yields elements one at a time. The memory footprint remains minimal and constant regardless of the number or size of the collections being combined.

Using chain.from_iterable()

When your input is already structured as an iterable of iterables (such as a list of lists or a generator of streams), passing them directly into chain() requires unpacking (chain(*nested_list)), which forces Python to evaluate the outer collection into memory before passing it as arguments.

To handle pre-nested iterables efficiently, use the class method chain.from_iterable():

from itertools import chain

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

flattened = chain.from_iterable(nested_data)

print(list(flattened))
# Output: [1, 2, 3, 4, 5, 6]

chain.from_iterable() lazily pulls each sub-iterable only when needed, making it suitable for flattening streams or generators of unknown or infinite length.