Find all subsets of length k in an array
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Finding all subsets of length k is the combinations problem: choose k elements from an array without caring about order. It appears in search, recommendation, backtracking, test generation, and any workflow that needs every possible fixed-size group.
What the problem really asks for
If the input is [1, 2, 3, 4] and k is 2, the valid results are [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], and [3, 4]. Order does not matter, so [1, 2] and [2, 1] are the same subset and should not both appear.
That detail determines the algorithm. You want combinations, not permutations.
The number of results is the binomial coefficient n choose k, so any correct algorithm must spend time proportional to the number of subsets it emits. The goal is not to avoid that cost, but to generate the subsets cleanly and without duplicates.
A standard backtracking solution
Backtracking is the clearest hand-written approach. Build the current subset, recurse forward through the remaining elements, and stop when the subset reaches size k.
This works because each recursive call moves the start index forward. Once an element has been used, later calls only consider elements to its right, which naturally prevents duplicate combinations.
The same pattern adapts well to strings, objects, or filtered searches. If you later need extra constraints, such as a target sum or a forbidden pair, backtracking gives you a natural place to add pruning logic.
When a library is enough
In Python, the standard library already provides this functionality through itertools.combinations.
For most production code, this is the best choice. It is concise, tested, and communicates intent immediately. A custom recursive solution is still useful when you need to understand the algorithm, port it to another language, or add custom pruning behavior.
Edge cases that matter
If k is 0, the correct result is usually one empty subset. If k is larger than the array length, the correct result is no subsets at all. Handling those cases deliberately keeps your function predictable.
It also matters whether the input array contains duplicates. The backtracking code above treats positions as distinct. If the input is [1, 1, 2], you may get repeated value combinations unless you add duplicate-skipping logic after sorting.
That is not a bug in the algorithm. It is a question of how you define uniqueness: by position or by value.
Complexity and practical limits
The time cost is proportional to the number of emitted subsets times the cost of copying each subset. In big notation, that is commonly described as O(C(n, k) * k). The important practical fact is simpler: the output itself can become enormous very quickly.
For example, choosing 10 items from 30 already produces more than thirty million combinations. If you do not actually need all of them stored at once, consider yielding them one by one as a generator instead of building one giant list in memory.
Common Pitfalls
- Generating permutations instead of combinations and accidentally producing duplicates in different orders.
- Forgetting to move the start index forward, which allows the same element position to be reused incorrectly.
- Ignoring edge cases such as
k == 0ork > len(items). - Storing every subset in memory when a generator-style approach would be safer.
- Expecting a duplicates-containing input array to produce unique value combinations without extra logic.
Summary
- Fixed-length subsets are combinations, not permutations.
- Backtracking is the standard manual solution and is easy to extend with pruning rules.
- In Python,
itertools.combinationsis usually the most practical choice. - The output size grows combinatorially, so memory use can become the real limit.
- Be explicit about edge cases and about whether duplicate input values should collapse into unique results.

