Python
Generators
Iterables
Join Iterables
Python Programming

How to join two generators or other iterables in Python?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Joining iterables in Python can mean different operations: concatenation, pairwise zipping, sorted merge, or alternating interleave. The correct tool depends on the output shape you need and whether laziness matters. For generator heavy code, using iterator primitives correctly keeps memory usage low and pipelines predictable.

Sequential Concatenation with itertools.chain

If you want all values from iterable A followed by all values from iterable B, use itertools.chain.

python
1from itertools import chain
2
3def first():
4    for x in [1, 2, 3]:
5        yield x
6
7def second():
8    for x in [4, 5]:
9        yield x
10
11joined = chain(first(), second())
12print(list(joined))  # [1, 2, 3, 4, 5]

chain is lazy, so values are produced only when consumed.

When you have many iterables stored in a list, use chain.from_iterable:

python
1from itertools import chain
2
3parts = [range(2), [10, 11], (20, 21)]
4print(list(chain.from_iterable(parts)))

This avoids unpacking large argument lists.

Custom Composition with yield from

For reusable custom behavior, create your own generator wrapper.

python
1def join_iterables(*iterables):
2    for it in iterables:
3        yield from it
4
5print(list(join_iterables([1, 2], (3, 4), range(5, 7))))

This is functionally similar to chain, but easy to extend with logging or conditional routing.

Pairwise Combination Is a Different Operation

Many questions about joining are actually about pairing elements by position. Use zip for that.

python
names = ["Ana", "Ben", "Cara"]
scores = [91, 88, 95]
print(list(zip(names, scores)))

If iterables have different lengths and you want all elements, use zip_longest:

python
from itertools import zip_longest

print(list(zip_longest([1, 2], ["x"], fillvalue=None)))

This is not concatenation. It changes output structure to tuples.

Sorted Merge for Already Sorted Streams

If both streams are sorted and you need one sorted output, use heapq.merge.

python
1import heapq
2
3a = [1, 4, 7]
4b = [2, 3, 10]
5
6for x in heapq.merge(a, b):
7    print(x, end=" ")

heapq.merge is lazy and avoids materializing or sorting the full combined list.

Interleaving Two Iterables

Sometimes you need alternating values from each iterable.

python
1def interleave(a, b):
2    ia, ib = iter(a), iter(b)
3    while True:
4        progressed = False
5        try:
6            yield next(ia)
7            progressed = True
8        except StopIteration:
9            pass
10        try:
11            yield next(ib)
12            progressed = True
13        except StopIteration:
14            pass
15        if not progressed:
16            break
17
18print(list(interleave([1, 3, 5], [2, 4])))

This pattern is useful in stream scheduling and display formatting.

Generator Exhaustion and Reuse

Generators are one pass iterators. Once consumed, they cannot be reused.

python
g = (n for n in [1, 2, 3])
print(list(g))  # [1, 2, 3]
print(list(g))  # []

If you need repeated iteration, either recreate the generator or store results in a list intentionally.

Infinite Streams and Safe Consumption

When combining finite and infinite iterables, stay lazy and use bounded consumers such as islice.

python
1import itertools
2
3naturals = itertools.count(1)
4finite_prefix = [100, 200]
5joined = itertools.chain(finite_prefix, naturals)
6
7print(list(itertools.islice(joined, 8)))

Avoid converting infinite or very large joins into a full list.

Common Pitfalls

A frequent mistake is using zip when sequential concatenation was needed. The output shape becomes tuple pairs, which breaks downstream logic.

Another issue is exhausting a generator in debug prints, then seeing no values in actual processing.

Teams also materialize large pipelines too early with list(...), losing the memory advantage of iterator composition.

Summary

  • Use itertools.chain for lazy sequential joining.
  • Use yield from when you want custom composable wrappers.
  • Use zip or zip_longest for pairwise alignment, not concatenation.
  • Use heapq.merge for lazy sorted merges.
  • Respect one pass generator behavior and keep pipelines lazy when possible.

Course illustration
Course illustration

All Rights Reserved.