Algorithm

Permutations

Backtracking Pattern

Permutations

Given an array of distinct integers, return every possible ordering of those integers.

Every permutation uses all of the elements exactly once — none omitted, none repeated. Two permutations differ if the elements appear in a different sequence, so [1,2,3] and [2,1,3] are both valid and distinct answers.

The output contains n! permutations and may be returned in any order.

CONSTRAINTS
  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • All integers in nums are distinct
  • Every permutation must use every element exactly once
  • Output size is n!, which is 720 at the maximum input length
EXAMPLE 1
Input: nums = [1,2,3]
Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
Six orderings, matching 3! = 6. Unlike combinations, [1,2,3] and [2,1,3] are both present — here rearranging genuinely produces a different answer.
EXAMPLE 2
Input: nums = [0,1]
Output: [[0,1],[1,0]]
Two elements give two orderings. Every element appears in every permutation; the only thing that varies is where.
EXAMPLE 3
Input: nums = [1]
Output: [[1]]
A single element has exactly one ordering. Worth checking that the base case returns a list containing one list, rather than an empty result or a bare value.
EXAMPLE 4
Input: nums = [-1,2,-3]
Output: All 6 orderings of -1, 2 and -3
The values themselves never affect the structure. Permutation count depends only on how many elements there are, so negatives and ordering of the input change nothing about the answer's size or shape.
Are the input integers guaranteed distinct?
Yes here, which keeps the problem clean. With duplicates, [1,1,2] would generate identical permutations by different routes and you would need to sort the input and skip a value when it equals its predecessor and that predecessor is unused — that variant is Permutations II.
Must every permutation use all the elements?
Yes. Permutations of a chosen subset — arrangements of k out of n — is a different problem that combines this technique with a length cutoff.
Does the order of the permutations in the output matter?
No, any order is accepted, so no sorting step is needed. The natural recursion order happens to be lexicographic when the input is sorted, which is convenient but not required.
How large can the output get?
n! grows viciously — 6! is 720, but 10! is over 3.6 million and 13! exceeds a billion. The tight constraint of 6 is a signal that exponential enumeration is expected and acceptable.

Set this beside Combinations before writing anything, because the two problems look similar and the difference between them determines the entire solution.

Combinations asks which elements to select, and rearranging a selection produces nothing new. Permutations asks how to arrange elements, and rearranging is the only thing that produces anything new. Every element is used in every answer; only position varies.

That single difference removes one mechanism and requires another.

Why the start index disappears

Combinations avoided duplicates by only ever choosing numbers larger than the last one taken, forcing every path into increasing order so each set had exactly one representation.

Apply that here and it destroys the problem. Forcing increasing order means [1,2,3] is buildable but [2,1,3] is not — yet [2,1,3] is a required answer. The very orderings the start index suppresses are the ones we now need.

So the constraint has to be dropped. At every position, any element is a legal choice.

Any element, that is, except the ones already placed. A permutation uses each element exactly once, so the restriction is no longer "later than the last choice" but "not already used". Different question, different bookkeeping.

Tracking what has been used

Maintain a parallel array of booleans, one per element, recording whether it currently sits in the path. Before choosing an element, check it; after backtracking, release it.

python
def permute(nums):
    result = []
    path = []
    used = [False] * len(nums)

    def build():
        if len(path) == len(nums):
            result.append(list(path))     # all slots filled: snapshot a copy
            return

        for i in range(len(nums)):
            if used[i]:
                continue                  # already placed on this path
            used[i] = True                # CHOOSE
            path.append(nums[i])
            build()                       # EXPLORE
            path.pop()                    # UN-CHOOSE
            used[i] = False               # release for other branches

    build()
    return result
Crucial Noteboth pieces of state must be undone, and forgetting used[i] = False is the classic bug. The path would unwind correctly while the used array stayed marked, so elements would be permanently consumed as the search proceeded and later branches would find nothing available. The symptom is an output far smaller than n! — often just a single permutation.
Crucial Noteused marks positions, not values. With distinct integers the distinction is invisible, but it matters the moment duplicates are allowed, where two positions hold the same value and must be treated as separate resources.
Crucial Notethe loop runs over every index from 0 each time, deliberately. There is no start index and adding one would be a bug — it is exactly what prevents the reorderings this problem exists to produce.
Why the count is n!

Count the branching rather than asserting the formula. At the first slot every element is available, giving n choices. Whichever is taken, the second slot has n-1 remaining, then n-2, and so on down to 1.

Multiplying: n × (n-1) × (n-2) × … × 1 = n!

Note the branching factor shrinks by one at each level, which is what distinguishes this tree from the subsets tree, where every level branched exactly twice. Producing each permutation also costs O(n) to copy, so the total is O(N × N!).

The n = 6 constraint is a deliberate signal. At 6 the answer has 720 entries; at 13 it would exceed a billion. When you see a tiny bound like this, the problem is telling you that exhaustive enumeration is the intended solution.

Worked example:nums = [1,2,3]

Following the first complete branch and the first backtrack in full:

- build(), path [], nothing used. Loop from i = 0.
- Choose 1 (index 0). used = [T,F,F], path [1].
- build(). Loop from i = 0: index 0 is used, skip.
- Choose 2. used = [T,T,F], path [1,2].
- build(). Indices 0 and 1 used, so only index 2 is available.
- Choose 3. path [1,2,3], length 3, record [1,2,3].
- Undo: pop 3, release index 2.
- No candidates remain, return.
- Undo: pop 2, release index 1. This release is what makes the next line possible.
- Choose 3 (index 2). used = [T,F,T], path [1,3].
- Only index 1 is free. Choose 2, record [1,3,2]. Undo both.
- Undo: pop 3, release index 2.
- Return.
- Undo: pop 1, release index 0 — now every element is free again, exactly as at the start.
- Choose 2 (index 1). The subtree beneath produces [2,1,3] and [2,3,1].
- Choose 3 (index 2). Produces [3,1,2] and [3,2,1].

Six permutations. Watch the moment marked above: releasing index 1 after the [1,2,…] subtree is what allows 2 to be reused in [1,3,2]. Skip that release and the search would produce [1,2,3] and then quietly stop finding anything else.

The swapping alternative

There is a second formulation that needs no auxiliary array. Rather than building a separate path, treat the input itself as the permutation under construction. At position i, swap each candidate from the remaining suffix into place, recurse on position i+1, then swap back.

python
def permute(nums):
    result = []

    def build(i):
        if i == len(nums):
            result.append(list(nums))     # nums IS the current permutation
            return
        for j in range(i, len(nums)):
            nums[i], nums[j] = nums[j], nums[i]     # CHOOSE
            build(i + 1)                            # EXPLORE
            nums[i], nums[j] = nums[j], nums[i]     # UN-CHOOSE
    build(0)
    return result

The elements before position i are fixed; those from i onward are the unused pool. Swapping is both the choice and the bookkeeping, so no used array is needed — the array's own layout encodes what remains.

It is more elegant and slightly faster. It is also harder to reason about and does not extend cleanly to inputs with duplicates, where the skip-condition is easier to express with an explicit used array. Knowing both, and being able to say why you picked one, is the useful position.

The three-way comparison

The section's core lesson is now visible. One skeleton, three problems, differing only in the choice space:

- Subsets — at each level, decide take or skip. Every path length is an answer.
- Combinations — at each level, choose a number greater than the last. Only length-k paths are answers.
- Permutations — at each level, choose any unused element. Only full-length paths are answers.

Deciding which you are facing reduces to one question: does rearranging an answer produce a different answer? If no, impose an ordering with a start index. If yes, track usage instead.

Train the reflex: order matters means no start index and a used array. Order does not matter means a start index and no used array. Getting that backwards produces either duplicates or missing answers, and it is the first thing to settle.

Interactive Strategy Visualization

The Arrangement Tree

Visualizing how the seating chart evolves with each pick.

START[]PICK 1PICK 2PICK 2PICK 1
Current Seat
Full Line-up
Start with all people standing. Every person is a candidate for the first chair.
THE USED POOL

Once a number is in a chair, it's Busy. We can't pick it again until it "stands up" (backtracks) and becomes Available once more.

ORDER MATTERS

Unlike subsets, picking [1, 2] and [2, 1] are two completely different realities. Every branch of the tree must lead to a full line-up.

O(Nⁿ) Generate All Sequences Then Filter
O(N × N!) Used-Array Backtracking
O(N × N!) In-place Swapping