Skip to content
Forge Learn/Backtracking

Classic Backtracking: Subsets & Permutations

6 min read

You'll learn to

  • -Implement subsets(nums) using the include/exclude backtracking pattern
  • -Implement permutations_of(nums) using a "used" tracking set
  • -Explain why subsets is O(2^n) while permutations is O(n!)

The previous chapter traced the recursion tree for subsets by hand. This chapter turns that trace into a clean, reusable function, then applies the same three-step template to a related but distinct problem: permutations, where order matters and every element must appear exactly once.

Subsets: Include or Exclude

Generating subsets is naturally framed as: for each remaining element, either include it in the current path or skip it, without ever reconsidering an element that came before. Passing `start` forward as `i + 1` is what enforces "never reconsider an earlier element," which is exactly why every subset is recorded once and never twice.

subsets(nums) - append a snapshot of path at every node, not just the leaves

Permutations: Order Matters, Nothing Repeats

Permutations flip the constraint: instead of "never look backward," the rule is "use every element exactly once, in every possible order." That needs a different bookkeeping tool - a `used` array (or set) tracking which elements are already part of the current path, so the loop can skip anything already placed instead of restricting which index it starts from.

permutations_of(nums) - a "used" set replaces the start index from subsets

Why O(2^n) and Why O(n!)

In subsets, every one of the n elements independently is either in a given subset or not - two choices per element, made independently, giving 2ⁿ total subsets. In permutations, the count is different because the choices are not independent: the first position can be any of n elements, the second position can be any of the remaining n - 1, the third any of the remaining n - 2, and so on down to 1 - giving n × (n - 1) × (n - 2) × ... × 1 = n! total orderings. Both algorithms additionally spend O(n) time copying `path` into `result` at each valid solution, so the tight complexity is O(n · 2ⁿ) and O(n · n!) respectively - but the dominant, headline term everyone quotes is the count of solutions itself.

  • -subsets(nums): 2ⁿ possible subsets, since each of n elements is independently in or out.
  • -permutations_of(nums): n! possible orderings, since the number of remaining choices shrinks by one at every level of the recursion.
  • -Both are worse than exponential growth can hide well for small n (n=10 gives 1,024 subsets but 3,628,800 permutations) - always sanity-check n before assuming a brute-force search will finish in reasonable time.

n! grows dramatically faster than 2ⁿ - permutations of just 15 elements is already over a trillion. Never brute-force permutations for anything but small, bounded n.

Check Yourself1 / 3

What does the "un-choose" step (e.g. path.pop()) accomplish in the backtracking template?

Ready to Build This?

Level 68: Backtracking Basics asks you to implement subsets(nums) and permutations_of(nums) using exactly the choose/explore/un-choose template from these two chapters - include/exclude with a forward-only start index for subsets, and a used-tracking set for permutations.

ScaleDojo Logo
Initializing ScaleDojo