combinations
lists
cartesian product
programming
algorithms

Generate all combinations from multiple lists

Master System Design with Codemia

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

Introduction

Generating all combinations from multiple lists is the cartesian-product problem. If you have several collections and want one element from each, every possible tuple belongs to the result set. This is simple conceptually, but the implementation details matter once the number of lists grows or the result size becomes very large.

Understand the Cartesian Product

If the input lists are:

  1. [1, 2]
  2. ['x', 'y']
  3. ['I', 'II']

then the combinations are:

text
1(1, 'x', 'I')
2(1, 'x', 'II')
3(1, 'y', 'I')
4(1, 'y', 'II')
5(2, 'x', 'I')
6(2, 'x', 'II')
7(2, 'y', 'I')
8(2, 'y', 'II')

Each tuple contains one choice from each input list. The order of lists matters because the first position comes from the first list, the second from the second list, and so on.

Use itertools.product in Python

In Python, the most direct and readable solution is itertools.product.

python
1from itertools import product
2
3lists = [
4    [1, 2],
5    ["x", "y"],
6    ["I", "II"]
7]
8
9for combo in product(*lists):
10    print(combo)

This is the standard library implementation of the cartesian product. It is concise and avoids reimplementing nested loops manually.

If you want a materialized list instead of an iterator:

python
1from itertools import product
2
3result = list(product([1, 2], ["x", "y"], ["I", "II"]))
4print(result)

For small inputs this is convenient. For larger inputs, keeping the result as an iterator is often better.

Build It Recursively When You Need Custom Logic

If you want to filter, transform, or stop early in a custom way, a recursive solution can be helpful.

python
1def combinations(lists, prefix=None):
2    if prefix is None:
3        prefix = []
4
5    if not lists:
6        yield tuple(prefix)
7        return
8
9    first, rest = lists[0], lists[1:]
10    for item in first:
11        prefix.append(item)
12        yield from combinations(rest, prefix)
13        prefix.pop()
14
15for combo in combinations([[1, 2], ["x", "y"], ["I", "II"]]):
16    print(combo)

This structure mirrors the problem definition directly: choose one element from the first list, then recursively choose from the remaining lists.

Understand the Growth of the Result Size

The total number of combinations is the product of the list lengths. If the lengths are a, b, and c, then the result size is a * b * c.

That means the main difficulty is not the loop logic. It is the combinatorial explosion.

For example:

  1. 3 lists of length 10 give 1,000 combinations.
  2. 6 lists of length 10 give 1,000,000 combinations.

This is why generator-based approaches are often preferable. They let you process combinations one at a time instead of trying to hold everything in memory.

Handle Empty Lists Deliberately

If any input list is empty, the entire product is empty because no complete tuple can be formed.

python
from itertools import product

print(list(product([1, 2], [], ["x", "y"])))

That returns an empty list. This behavior is correct, but it is worth calling out because it surprises people when the rest of the lists are non-empty.

Prefer Iteration Over Hardcoded Nested Loops

Manual nested loops work only when the number of lists is fixed in advance. As soon as the list count is dynamic, hardcoded loops become unmaintainable.

That is why recursive generation or library tools such as itertools.product are better. They solve the general problem once instead of rewriting the same logic for three lists, four lists, five lists, and so on.

Common Pitfalls

  • Materializing the entire product into memory when an iterator would be sufficient.
  • Forgetting that the total result size grows as the product of the list lengths.
  • Using hardcoded nested loops for a problem where the number of input lists can vary.
  • Expecting non-empty output when one of the input lists is empty.
  • Confusing combinations from multiple lists with combinations chosen from one list without replacement.

Summary

  • Generating all combinations from multiple lists is the cartesian-product problem.
  • In Python, itertools.product is the clearest standard solution.
  • Recursive generation is useful when you need custom traversal behavior.
  • The real challenge is result-size growth, not syntax.
  • Prefer generators when the product may be large and you do not need every tuple in memory at once.

Course illustration
Course illustration

All Rights Reserved.