How to Generate Combinatoric Sequences Using itertools in Python
Python's itertools module provides four powerful functions for generating combinatoric sequences efficiently. Each one serves a distinct mathematical purpose, and choosing the right one depends on two questions: does order matter? and are repeated elements allowed?
In this guide, you will learn how each function works, understand the mathematical differences between them, and see practical examples that demonstrate when to use each one.
product() for Cartesian Products
The product() function generates every possible pairing across one or more iterables. Order matters and repetition is allowed:
from itertools import product
# Two iterables
result = list(product([1, 2], ["A", "B"]))
print(result)
Output:
[(1, 'A'), (1, 'B'), (2, 'A'), (2, 'B')]
Every element from the first iterable is paired with every element from the second. For three or more iterables, the same principle extends:
from itertools import product
result = list(product("AB", "CD", "EF"))
print(f"Total combinations: {len(result)}")
print(result[:4])
Output:
Total combinations: 8
[('A', 'C', 'E'), ('A', 'C', 'F'), ('A', 'D', 'E'), ('A', 'D', 'F')]
The repeat parameter computes the Cartesian product of an iterable with itself:
from itertools import product
# All 3-bit binary numbers
binary = list(product([0, 1], repeat=3))
print(binary)
Output:
[(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)]
Common use cases: password generation, grid coordinates, exhaustive test configuration.
permutations() for Ordered Arrangements
The permutations() function generates all possible arrangements of r items from the input, where order matters but each element is used at most once:
from itertools import permutations
# All 2-item permutations from 3 elements
result = list(permutations("ABC", 2))
print(result)
Output:
[('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]
Notice that both ('A', 'B') and ('B', 'A') appear because order matters. When r is omitted, it defaults to the full length of the input:
from itertools import permutations
# All arrangements of 3 items
result = list(permutations("ABC"))
print(result)
print(f"Total: {len(result)}")
Output:
[('A', 'B', 'C'), ('A', 'C', 'B'), ('B', 'A', 'C'), ('B', 'C', 'A'), ('C', 'A', 'B'), ('C', 'B', 'A')]
Total: 6
Common use cases: anagram generation, seating arrangements, route planning.
combinations() for Unordered Selections
The combinations() function generates subsets of r items where order does not matter. Each element is used at most once:
from itertools import combinations
result = list(combinations("ABCD", 2))
print(result)
Output:
[('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D'), ('C', 'D')]
Unlike permutations, ('A', 'B') and ('B', 'A') are considered the same selection, so only one appears:
from itertools import combinations
# Choose 3 from 4
result = list(combinations([1, 2, 3, 4], 3))
print(result)
Output:
[(1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)]
Common use cases: poker hands, team selection, lottery number combinations.
combinations_with_replacement() for Repeated Selections
The combinations_with_replacement() function works like combinations() but allows the same element to appear multiple times in each result:
from itertools import combinations_with_replacement
result = list(combinations_with_replacement("AB", 2))
print(result)
Output:
[('A', 'A'), ('A', 'B'), ('B', 'B')]
The pair ('A', 'A') is now included because repetition is allowed:
from itertools import combinations_with_replacement
result = list(combinations_with_replacement([1, 2, 3], 2))
print(result)
Output:
[(1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)]
Common use cases: coin combinations, dice sum analysis, multiset problems.
Visual Comparison
To see the differences clearly, here are all three selection-based functions applied to the same input with r=2:
from itertools import permutations, combinations, combinations_with_replacement
items = ["A", "B", "C"]
perms = list(permutations(items, 2))
combs = list(combinations(items, 2))
combs_rep = list(combinations_with_replacement(items, 2))
print(f"permutations ({len(perms)}): {perms}")
print(f"combinations ({len(combs)}): {combs}")
print(f"combinations_with_replacement ({len(combs_rep)}): {combs_rep}")
Output:
permutations (6): [('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]
combinations (3): [('A', 'B'), ('A', 'C'), ('B', 'C')]
combinations_with_replacement (6): [('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'B'), ('B', 'C'), ('C', 'C')]
Permutations treat ('A', 'B') and ('B', 'A') as different results because order matters. Combinations treat them as the same selection and include only one.
Counting Results Without Generating Them
You can calculate the number of results mathematically using Python's math module, which is useful for estimating the size of a result set before generating it:
from math import comb, perm
n, r = 5, 3
print(f"product: {n ** r}")
print(f"permutations: {perm(n, r)}")
print(f"combinations: {comb(n, r)}")
print(f"combinations_with_replacement: {comb(n + r - 1, r)}")
Output:
product: 125
permutations: 60
combinations: 10
combinations_with_replacement: 35
Practical Examples
Generating All Possible PINs
from itertools import product
pin_count = 0
for pin in product(range(10), repeat=4):
pin_count += 1
print(f"Total 4-digit PINs: {pin_count}")
Output:
Total 4-digit PINs: 10000
Finding Anagrams of a Word
from itertools import permutations
word = "cat"
anagrams = ["".join(p) for p in permutations(word)]
print(anagrams)
Output:
['cat', 'cta', 'act', 'atc', 'tca', 'tac']
Counting Lottery Combinations
from math import comb
# Choose 6 numbers from 49
total_tickets = comb(49, 6)
print(f"Possible lottery tickets: {total_tickets:,}")
Output:
Possible lottery tickets: 13,983,816
Memory Efficiency
All four functions return iterators, not lists. They generate results lazily, one at a time, with O(1) startup time. This makes them safe to use even when the total number of results is extremely large:
from itertools import product
# 10 billion combinations - perfectly safe to iterate
huge = product(range(10), repeat=10)
# Only generates values as the loop requests them
first_five = []
for i, combo in enumerate(huge):
first_five.append(combo)
if i >= 4:
break
print(first_five)
Output:
[(0, 0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 0, 1), (0, 0, 0, 0, 0, 0, 0, 0, 0, 2), (0, 0, 0, 0, 0, 0, 0, 0, 0, 3), (0, 0, 0, 0, 0, 0, 0, 0, 0, 4)]
Avoid calling list() on large combinatoric results. Converting 10 billion tuples to a list would exhaust your system's memory. Iterate directly over the iterator instead.
Quick Reference
| Function | Order Matters | Repeats Allowed | Formula | Example Use Case |
|---|---|---|---|---|
product | Yes | Yes | n^r | Passwords, grids, test configs |
permutations | Yes | No | n! / (n-r)! | Anagrams, routes, rankings |
combinations | No | No | n! / (r! (n-r)!) | Poker hands, teams, lotteries |
combinations_with_replacement | No | Yes | (n+r-1)! / (r! (n-1)!) | Coins, dice, multisets |
Conclusion
These four itertools functions cover all standard combinatoric patterns.
- Use
productfor exhaustive cross-iteration across multiple iterables - Use
permutationswhen the arrangement order matters - Use
combinationsfor unordered selections without repetition - use
combinations_with_replacementwhen the same element can appear multiple times.
All four return lazy iterators that generate results on demand, making them memory-efficient for even the largest combinatoric spaces.