combinatorics
subsets
algorithms
array manipulation
programming techniques

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.

python
1def subsets_of_length_k(items, k):
2    result = []
3    current = []
4
5    def backtrack(start):
6        if len(current) == k:
7            result.append(current.copy())
8            return
9
10        for index in range(start, len(items)):
11            current.append(items[index])
12            backtrack(index + 1)
13            current.pop()
14
15    backtrack(0)
16    return result
17
18
19values = [1, 2, 3, 4]
20print(subsets_of_length_k(values, 2))

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.

python
1from itertools import combinations
2
3values = [1, 2, 3, 4]
4print(list(combinations(values, 2)))

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 == 0 or k > 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.combinations is 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.

Course illustration
Course illustration

All Rights Reserved.