Recursion & Backtracking · Recursion & Backtracking Fundamentals

Backtracking Permutation Generation

Building every permutation of a fixed set by choosing, at each position, any not-yet-used element - tracked with a simple "used" marker rather than removing and restoring elements from a working list.

  • 6 min read
  • 9 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: duplicates in the input
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

A permutation uses every element exactly once, in some order. Unlike with subsets, which elements are included is fixed; only the order varies. At each position, any element not yet placed could come next, including ones earlier in the input. There are n! permutations (5,040 for n = 7), so the job is to list them without missing or repeating any. This skill covers the used[] backtracking, duplicates, and the iterative generators real code uses.

We'll go from simple to more technical:

  1. The pattern: a used[] array, so any unplaced element can go next.
  2. A second flavor: duplicates, with sorting and equal copies taken in order.
  3. Where permutation search shows up (small schedules, brute-force checks).
  4. What breaks it: the subsets loop, missing releases, and unsorted dedup.

The approach

Fill the positions one at a time. For each position, try every element that isn't placed yet, and recurse to fill the rest. A used[] array remembers which elements are already in the current arrangement. After the recursive call returns, take the element back out and mark it free again, so the next branch can use it. That undo step is the "backtracking".

For [1, 2, 3], the choices form a tree. Each level picks one unused element:

start

1

2

3

1 2

1 2 3

1 3

1 3 2

2 1

2 1 3

2 3

2 3 1

3 1

3 1 2

3 2

3 2 1

start

1

2

3

1 2

1 2 3

1 3

1 3 2

2 1

2 1 3

2 3

2 3 1

3 1

3 1 2

3 2

3 2 1

Java

void backtrack(int[] nums, boolean[] used, List<Integer> current, List<List<Integer>> result) {
    if (current.size() == nums.length) {
        result.add(new ArrayList<>(current));          // every element placed
        return;
    }
    for (int i = 0; i < nums.length; i++) {             // every position considers ALL elements...
        if (used[i]) continue;                          // ...except the ones already placed
        used[i] = true;
        current.add(nums[i]);
        backtrack(nums, used, current, result);
        current.remove(current.size() - 1);
        used[i] = false;                                // available again for other orderings
    }
}

Python

def backtrack(nums: list[int], used: list[bool], current: list[int], result: list[list[int]]) -> None:
    if len(current) == len(nums):
        result.append(current[:])                      # every element placed
        return
    for i in range(len(nums)):                         # every position considers ALL elements...
        if used[i]:
            continue                                   # ...except the ones already placed
        used[i] = True
        current.append(nums[i])
        backtrack(nums, used, current, result)
        current.pop()
        used[i] = False                                # available again for other orderings

The loop always starts at 0. That's the difference from subsets, whose loop only looks forward and so can only keep elements in their original order. With sorted input, trying elements in index order produces the permutations in dictionary order, as in the tree above.

A second flavor: duplicates in the input

With repeated values, swapping two equal elements gives the same permutation twice. [1, 1, 2] has 6 orderings of its positions but only 3 distinct permutations: 1 1 2, 1 2 1 and 2 1 1. The fix: sort the input so equal values sit together, then only let equal copies be placed in their original left-to-right order.

Java

Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
    if (used[i]) continue;
    if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue;   // take equal copies in order
    ...
}

Python

nums.sort()
for i in range(len(nums)):
    if used[i]:
        continue
    if i > 0 and nums[i] == nums[i - 1] and not used[i - 1]:
        continue                                       # take equal copies in order
    ...

!used[i - 1] means the earlier equal copy isn't placed yet. Using this copy first would only repeat an ordering the earlier copy already produces. The sort matters: in [1, 2, 1] the two 1s aren't neighbors, the check never fires, and every permutation appears twice.

Where you'll see it

Testing every order of a few concurrent operations, to shake out race conditions, lists permutations. So does trying every order of five steps in a small pipeline. Factorial growth limits it to a handful of elements. Past about a dozen, real systems switch to heuristics or dynamic programming.

Which tool for which job?

You needUse
Every permutation, in lexicographic orderused[] backtracking on sorted input
Distinct permutations with repeated valuessort, then skip an equal copy whose predecessor is unused
The next arrangement after this onethe next-permutation algorithm, O(1) extra space
Each permutation one swap from the lastHeap's algorithm
Real code, no duplicates in the inputitertools.permutations (lazy)
One permutation at a time, not all storeda generator, iterator or callback

Common mistakes

MistakeWhat happensDo this instead
The subsets start-index looponly 1 2 3 reaches full lengthloop over every index, skip used ones
Forgetting used[i] = falseonly the first arrangement is builtrelease after each recursive call
Deduplicating without sortingequal values aren't adjacent; duplicates appearsort first
itertools.permutations on repeated values40,320 tuples for 28 distinct ordersdedup backtracking or next-permutation
Storing all n! resultsgigabytes at n = 10process them one at a time

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Why can't permutations use the start-index loop from subsets?

Show answer

The start index only allows elements after the last one chosen, so elements never appear out of their original order. For [1, 2, 3] only 1 2 3 is produced. Permutations need every unused element as a candidate at every position, which the loop from 0 plus used[] gives.

2. What does nums[i] == nums[i - 1] && !used[i - 1] skip, and why does it need sorted input?

Show answer

It skips a copy of a value while the earlier equal copy is still unused. So equal copies always enter in index order, and each distinct permutation is built once. It only compares neighbors, so equal values must sit together, which is what sorting guarantees.

3. What goes wrong if used[i] = false is left out?

Show answer

Elements stay marked as used forever. After the first arrangement is complete, nothing is free for later branches, so for [1, 2, 3] only 1 2 3 comes out.

Key takeaways

  • At every position, try every unused element: loop from 0 and track used[].
  • Mark used[i] before recursing and clear it after. Record a copy of the working list, not the list itself.
  • On sorted input, index order gives the permutations in dictionary order.
  • For duplicates, sort, then skip a value whose earlier equal copy is unused.
  • With n! results, O(n * n!) is unavoidable. Keep n small, or produce permutations one at a time.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
All permutations, sortedused[] backtracking
Distinct with duplicatessort plus "equal copies in order"
The next onenext permutation
One swap per stepHeap's algorithm
Python, distinct valuesitertools.permutations

Snippets worth knowing by heart

Java

boolean next(int[] p) {
    int i = p.length - 2;
    while (i >= 0 && p[i] >= p[i + 1]) i--;
    if (i < 0) return false;                         // last permutation
    int j = p.length - 1;
    while (p[j] <= p[i]) j--;
    swap(p, i, j);
    for (int l = i + 1, r = p.length - 1; l < r; l++, r--) swap(p, l, r);
    return true;
}

Python

for i in range(n):
    if used[i] or (i > 0 and nums[i] == nums[i - 1] and not used[i - 1]):
        continue
    used[i] = True
    cur.append(nums[i])
    backtrack()
    cur.pop()
    used[i] = False

The traps

  • The subsets loop: starting the loop at start builds only one full-length order. Loop over every index and skip used ones.
  • No release of used[i]: only the first arrangement is ever built. Set it back to false after the call.
  • Dedup without sorting: equal values aren't adjacent, so duplicates slip through. Sort first.
  • itertools.permutations with repeats: it yields every position order, duplicates included. Use dedup backtracking or next-permutation.
  • Storing n! results: memory runs out around n = 10. Process each permutation as it's built.

Staff-level points, one line each

  • Next permutation over 8!: 40,320 steps, 3.09 element writes per step on average. It handles duplicates for free.
  • Heap's algorithm: 40,320 distinct permutations, each one swap from the last.
  • Six 0s and two 1s: itertools gave 40,320 tuples for 28 orders; dedup backtracking gave 28 in 119 calls.
  • 10! = 3,628,800 and 12! = 479,001,600: process permutations, don't store them.

Say it in an interview

  • "Any unused element can go next, so I keep a used array."
  • "With duplicates I sort and take equal copies in index order."
  • "Next permutation gives lexicographic order iteratively, in O(1) extra space."
  • "The output is n! × n, so that's the floor on the running time."

Practice

This skill comes with 9 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Backtracking Permutation Generation free

Print All Permutations of an Array

MediumProCoding problemJava · Python

Given an array nums of n distinct integers, sorted ascending, return all possible permutations, in lexicographic order.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Permutations II (With Duplicates)

MediumProCoding problemJava · Python

Given an array nums that may contain duplicate values (in any order), return every distinct permutation: each different ordering of values exactly once.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Backtracking Permutation Generation free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime