itertools.combinations() is one of Python’s most useful tools from the itertools module — it generates all possible combinations (subsets) of a given length from an iterable, without regard to order, and returns them as tuples in lexicographic sort order. Unlike manual nested loops, combinations() is lazy (yields one combo at a time), memory-efficient (constant space), implemented in C for speed, and avoids duplicates or repeated work. In 2026, itertools.combinations() remains essential — used constantly for combinatorial search, subset generation, feature selection, testing all possibilities, lottery simulations, and data analysis tasks where you need to pick k items from n without repetition or order mattering.
Here’s a complete, practical guide to itertools.combinations(): basic usage, handling large inputs, real-world patterns, performance advantages over loops, and modern best practices with type hints, slicing, and scalability.
The syntax is simple: itertools.combinations(iterable, r) — iterable can be any sequence or iterator, r is the combination length (r ? len(iterable)). It yields tuples in sorted order based on input order.
import itertools
lst = [1, 2, 3]
# All combinations of length 2
for comb in itertools.combinations(lst, 2):
print(comb)
# Output:
# (1, 2)
# (1, 3)
# (2, 3)
Convert to list only when needed — for large r or n, keep it lazy to save memory.
combs = list(itertools.combinations(range(10), 3)) # 120 combinations
print(len(combs)) # 120
print(combs[:3]) # [(0, 1, 2), (0, 1, 3), (0, 1, 4)]
Real-world pattern: generating pairs or groups for analysis, matching, or testing — combinations() is faster and cleaner than nested loops, especially for moderate n and r.
# Find all pairs of users with similar scores
users = [("Alice", 85), ("Bob", 92), ("Charlie", 78), ("Dana", 88)]
for (user1, score1), (user2, score2) in itertools.combinations(users, 2):
if abs(score1 - score2) <= 10:
print(f"{user1} ({score1}) and {user2} ({score2}) are similar")
Another everyday use: feature selection, subset sum, or combinatorial search — combinations() scales well up to n ? 20–30 (depending on r) before memory/time explodes.
# Subset sums for small set
numbers = [1, 3, 5, 7]
target = 11
found = False
for subset in itertools.combinations(numbers, 3):
if sum(subset) == target:
print(f"Found: {subset}")
found = True
break
if not found:
print("No subset sums to target")
Best practices make combinations() usage efficient and readable. Prefer itertools.combinations() over manual nested loops — it’s clearer, faster (C-level), and lazy (constant memory). Use r wisely — for large n and r, combinations explode combinatorially (n choose r); use islice() to take only first n combos — itertools.islice(combinations(...), 1000). Add type hints for clarity — Iterator[tuple[int, ...]] — improves readability and mypy checks. Modern tip: use Polars for large combinatorial data — pl.int_range(n).join(..., how="cross") or pl.DataFrame(...).join(...) for Cartesian products. In production, wrap combinations() over external data in try/except — handle bad items gracefully. Avoid materializing large combinations — iterate lazily with for combo in combinations(...) or use takewhile/islice. Combine with enumerate() for indexed combinations — for i, combo in enumerate(combinations(lst, r), start=1).
itertools.combinations() unlocks combinatorial power — fast, memory-safe, and elegant for subsets, pairs, groups, and search. In 2026, use it instead of nested loops, keep r reasonable, slice lazily for large n, and add type hints for safety. Master combinations(), and you’ll solve selection, matching, and testing problems with speed and clarity.
Next time you need all possible subsets of a certain size — reach for itertools.combinations(). It’s Python’s cleanest way to say: “Give me every unique group, no repeats, no order.”