datarekha
Coding Patterns Medium Asked at AmazonAsked at GoogleAsked at Meta

Generate all subsets of a set — the power set — using backtracking.

The short answer

At each step of a recursive walk through the input, you make a binary choice: include the current element or skip it. Recording the current path at every node of the recursion tree (not just the leaves) collects all 2^n subsets. A `start` index prevents duplicates by ensuring elements are only considered left-to-right.

How to think about it

When a problem asks for every selection, arrangement, or partition and the answer count is exponential, the interviewer is testing one skill: can you frame it as a decision tree and walk it with backtracking? Subsets is the gentlest version of that test, because there is no constraint to prune against — every partial path is already a valid subset. So this is where you prove you can build a candidate incrementally, record it, and cleanly undo the last choice before trying the next.

Picture a tree over nums. At each level you decide whether to include the current element, and at every node — not just the leaves — the path you have built so far is one subset of the answer. The recursion captures it in three beats: choose (append nums[i]), explore (recurse from i + 1), un-choose (pop it back off). The start index is the quiet hero — by only ever looking forward from i + 1, it guarantees each element is considered left to right, so [1, 2] is generated but [2, 1] never is. That single rule is what keeps the 2ⁿ subsets unique.

A worked example

def subsets(nums):
    result = []

    def backtrack(start, path):
        result.append(list(path))      # every node is a valid subset -> copy it
        for i in range(start, len(nums)):
            path.append(nums[i])       # choose
            backtrack(i + 1, path)     # explore (start=i+1 -> only look forward)
            path.pop()                 # un-choose (backtrack)

    backtrack(0, [])
    return result

print(subsets([1, 2, 3]))
print(subsets([0]))
print(subsets([]))
print(len(subsets([1, 2, 3, 4])))     # 2^4 = 16

# The classic bug: append the path itself instead of a copy
def subsets_buggy(nums):
    result = []
    def backtrack(start, path):
        result.append(path)            # BUG: stores one shared list object
        for i in range(start, len(nums)):
            path.append(nums[i])
            backtrack(i + 1, path)
            path.pop()
    backtrack(0, [])
    return result

print("buggy result:", subsets_buggy([1, 2, 3]))
[[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]]
[[], [0]]
[[]]
16
buggy result: [[], [], [], [], [], [], [], []]

Two things to read. The correct run lists all eight subsets of [1, 2, 3], and subsets([]) returns [[]] — the empty set still has the empty subset, which is the base case doing its job. Then look at the buggy run: it collected the right number of entries (eight), but every one is []. That is the reference trap made visible — all eight pointed at the same path list, which was emptied back to [] by the time the recursion unwound.

Keep practising

All Coding Patterns questions

Explore further

Skip to content