Combinations (n choose k)
Enumerate all size-k subsets of n elements using the start-index template with a size-based base case and a "not enough elements left" prune.
Overview
A combination is an unordered selection of exactly k items from n; there are C(n, k) = n! / (k!(n−k)!) of them. The algorithm is the Subsets (Power Set) start-index template with two changes: record only when len(path) == k, and prune when the remaining elements cannot fill the path (n − i < k − len(path)).
Combination Sum generalizes the base case from "size equals k" to "sum equals target" and, when reuse is allowed, recurses with i instead of i + 1.
Intuition
A mental model before the formal terms.
Choosing 3 people from a line of 7: point at someone, then only consider people to their right for the next pick. That rule alone stops you from picking the same trio in a different order.
The prune is counting how many people remain to your right: if you still need 2 more picks and only 1 person is left, stop looking down this branch.
How it works
- Call
backtrack(start=0, path=[]). - If
len(path) == k, record a copy and return. - Loop
ifromstartwhilen − i ≥ k − len(path)(enough elements remain): pusha[i], recurse withi + 1, pop. - For combination sum with reuse: base case
remaining == 0; prune whena[i] > remainingafter sorting; recurse withi(noti + 1).
Why it works
Each k-combination corresponds to exactly one increasing index sequence of length k, which the start-index loop enumerates once.
The remaining-count prune is exact: once fewer than k − len(path) elements remain, no completion exists, so skipping is safe and cuts the tree from O(2^n) nodes to roughly O(C(n,k) · k).
Recognition
How to tell a problem wants this.
- "Choose exactly
k", "all combinations that sum to target", "select a group of sizek". - Order of chosen items is explicitly irrelevant — output
[1,2]but not[2,1]. - Small
n(≤ 20) and an output list of combinations is expected.
Interactive visualization
Play, step, change the input. ← → and space work too.
1go(start, current):2 if len(current) == k: record(current); return3 for x in start .. n:4 if n - x + 1 < k - len(current): break // prune: not enough left5 current.push(x); go(x + 1, current)6 current.pop() // backtrackPseudocode
1backtrack(start, path):2 if len(path) == k: record(copy of path); return3 for i in start..n-1:4 if n - i < k - len(path): break # not enough left5 path.push(a[i])6 backtrack(i + 1, path)7 path.pop()8backtrack(0, [])Implementations
1def combinations(n: int, k: int) -> list[list[int]]:2 """All k-combinations of 1..n."""3 out: list[list[int]] = []4 path: list[int] = []5 6 def backtrack(start: int) -> None:71 · Base case: k chosen8 if len(path) == k:9 out.append(path[:])10 return112 · Prune the loop bound12 need = k - len(path)13 for i in range(start, n - need + 2): # leave enough elements143 · Choose / explore / un-choose15 path.append(i)16 backtrack(i + 1)17 path.pop()18 19 backtrack(1)20 return out21 22 234 · Combination sum with unlimited reuse24def combination_sum(cands: list[int], target: int) -> list[list[int]]:25 cands = sorted(cands)26 out: list[list[int]] = []27 path: list[int] = []28 29 def backtrack(start: int, remaining: int) -> None:30 if remaining == 0:31 out.append(path[:])32 return33 for i in range(start, len(cands)):34 if cands[i] > remaining:35 break # sorted: nothing later fits36 path.append(cands[i])37 backtrack(i, remaining - cands[i]) # i, not i + 1: reuse allowed38 path.pop()39 40 backtrack(0, target)41 return outrange(start, n - need + 2)is the prune: the upper bound is exclusive, hence+ 2instead of the+ 1used in the other languages.out.append(path[:])stores a copy once k elements are chosen.backtrack(i + 1)incombinationsforbids reuse;backtrack(i, ...)incombination_sumallows it.breakoncands[i] > remainingis valid only becausecandsis sorted.
itertools.combinations(range(1, n + 1), k)produces the same output lazily in C;itertools.combinations_with_replacementcovers multisets.rangeupper bounds are exclusive, which is where the+ 2comes from.- Depth is at most k (or target / min candidate), far below the recursion limit.
- Off-by-one in the
rangebound after translating<= n - need + 1from another language. - Appending
pathinstead ofpath[:]. - Using
continueinstead ofbreakin the sorted prune, which is correct but wastes the pruning benefit.
- Python's
rangehas an exclusive upper bound, so the prune readsn - need + 2versusi <= n - need + 1in C++/JS/TS. - Only Python has a built-in combinations generator (
itertools); C++ can emulate withprev_permutationover a selector mask; JS/TS hand-roll it. - JS/TS require
sort((a, b) => a - b)before the sorted-prunebreakis valid.
Complexity
With the remaining-count prune every internal node leads to at least one leaf, so work is proportional to output size. Combination sum is exponential in target / min(candidate).
Compare growth rates in the Complexity Explorer →When to use — and when not to
- Enumerate all fixed-size selections, or all multisets summing to a target, for small inputs.
- Search over selections where a cheap prefix test (sum, size, remaining budget) prunes most branches.
- Only the count
C(n, k)is needed — compute it with Combinatorics (Pascal or multiplicative formula). - Counting the number of ways to reach a sum — Coin Change / Unbounded Knapsack DP does it in
O(n · target). - Selections must be ordered — that is Permutations.
Alternatives
Common mistakes
- Off-by-one in the prune bound
i <= n - need + 1(1-indexed) — test withk == n. - In combination sum, recursing with
i + 1when reuse is allowed (misses[2,2,3]) or withiwhen it is not (produces repeats). - Forgetting to sort before
break-ing onc[i] > remaining; with unsorted input usecontinueinstead. - Combination Sum II (each element once, duplicates in input) needs the Subsets (Power Set) duplicate skip
i > start && c[i] == c[i-1].
Interview patterns
- Combination Sum I / II / III: vary reuse, duplicate handling, and size constraint.
- Factor combinations, palindrome partitioning: the "element" is a chunk of the input, chosen from a start index.
- Phone-keypad letter combinations: cross product of per-position choice sets, no start index needed.
- Recursion versus iterationIntermediate
- Recognizing a dynamic-programming problemAdvanced
- Word SearchAdvanced