List every possible ordering of a set of distinct integers. The key move is classic backtracking: choose a number, recurse, then undo the choice — so every branch of the decision tree is explored exactly once.
Given an array of distinct integers, return all possible permutations (every full-length arrangement) in any order. nums = [1, 2, 3] produces six arrangements: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]. For n distinct numbers there are always exactly n! results.
n you are at a leaf — record it. After the recursive call returns, undo the choice (pop from the path, mark unused) so the same slot can try a different number. The backtrack step is what makes exhaustive search efficient: it reuses state instead of copying it on every branch.used[n] boolean mask (all false) and an empty current path. These are mutated in-place throughout.current.length === n push a snapshot ([...current]) into result and return. The spread is critical — without it every entry in result would alias the same array.i = 0..n-1. If used[i], skip. Otherwise: set used[i] = true, push nums[i] onto current, and call backtrack(current).current and reset used[i] = false. The state is exactly as it was before step 2 — the next iteration can safely try nums[i+1].result.push(current) stores a reference; every entry ends up pointing to the same array, which will be empty by the time you read it. Always push [...current].The output itself is O(n · n!) — you must produce n! permutations each of length n, so this is optimal. The call tree has O(n!) leaves and O(n · n!) total nodes.
Swap-in-place variant: instead of a used mask, swap nums[start] with each nums[i], recurse with start+1, then swap back. Avoids the mask but mutates the input array.
[1, 2, 3]. path is empty, all 3 numbers are free.1▶function permute(nums: number[]): number[][] {2▶ const result: number[][] = [];3▶ const used: boolean[] = new Array(nums.length).fill(false);45 function backtrack(current: number[]): void {6 if (current.length === nums.length) {7 result.push([...current]); // leaf: full-length arrangement — record it8 return;9 }10 for (let i = 0; i < nums.length; i++) {11 if (used[i]) continue; // skip already-placed numbers12 used[i] = true;13 current.push(nums[i]);14 backtrack(current); // recurse one level deeper15 current.pop(); // undo the choice16 used[i] = false;17 }18 }1920▶ backtrack([]);21 return result;22}
function permute(nums: number[]): number[][] {
const result: number[][] = [];
const used: boolean[] = new Array(nums.length).fill(false);
function backtrack(current: number[]): void {
if (current.length === nums.length) {
result.push([...current]); // leaf: full-length arrangement — record it
return;
}
for (let i = 0; i < nums.length; i++) {
if (used[i]) continue; // skip already-placed numbers
used[i] = true;
current.push(nums[i]);
backtrack(current); // recurse one level deeper
current.pop(); // undo the choice
used[i] = false;
}
}
backtrack([]);
return result;
}result accumulates completed arrangements. used is a boolean mask: used[i] is true when nums[i] is already in the current path. Both are declared in the outer scope and mutated in-place — this is what makes the undo step possible.current.length === nums.length) we have a complete permutation. Push a snapshot ([...current]) and return. Without the spread every slot in result would alias the same array object, which ends up empty.used[i] is set, skip. Otherwise flag it used, append to current, and recurse. This is the “choose” half of the backtrack pair.O(n · n!) — there are n! leaves and each costs O(n) to record. Space: O(n) for the recursion stack and used mask (not counting the output).k by (remaining - 1)! to pick the digit directly — no backtracking needed.O(n) per step.function*) instead of collecting all n! into memory.nums[start] with nums[i], recurse on start + 1, swap back. Eliminates the used array but mutates the input.function permute(nums: number[]): number[][] {
const result: number[][] = [];
const used: boolean[] = new Array(nums.length).fill(false);
function backtrack(current: number[]): void {
if (current.length === nums.length) {
result.push([...current]); // leaf: full-length arrangement — record it
return;
}
for (let i = 0; i < nums.length; i++) {
if (used[i]) continue; // skip already-placed numbers
used[i] = true;
current.push(nums[i]);
backtrack(current); // recurse one level deeper
current.pop(); // undo the choice
used[i] = false;
}
}
backtrack([]);
return result;
}O(n · n!) — there are n! leaves and each costs O(n) to record. Space: O(n) for the recursion stack and used mask (not counting the output).Enumerate every sequence of n positions where each position independently picks any of the n values (so nⁿ tuples in all), then discard any tuple that reuses an element. What survives is exactly the set of permutations.
function permute(nums: number[]): number[][] {
const n = nums.length;
const result: number[][] = [];
const current: number[] = [];
function build(slot: number): void {
if (slot === n) {
// Keep only tuples that used n distinct elements.
if (new Set(current).size === n) result.push([...current]);
return;
}
for (let i = 0; i < n; i++) {
current.push(nums[i]); // no used-check: every value is allowed here
build(slot + 1);
current.pop();
}
}
build(0);
return result;
}used mask the search explores all nⁿ tuples and throws away the overwhelming majority (only n! of them are valid) — e.g. for n = 8 that is 16.7M tuples to surface 40K answers. Skipping already-placed elements with the used[] mask prunes every dead branch up front, recovering the optimal O(n · n!).| "all arrangements / orderings" | backtrack with used[] mask |
| choose one from remaining, repeat until full | backtrack choose/recurse/undo |
| "n! results" in constraints | backtrack leaf-record pattern |
| subsets / combinations / permutations family | same skeleton, different base/loop |
const result: number[][] = [];
const used: boolean[] = new Array(nums.length).fill(false);
function backtrack(current: number[]): void {
if (current.length === nums.length) {
result.push([...current]);
return;
}
for (let i = 0; i < nums.length; i++) {
if (used[i]) continue;
used[i] = true;
current.push(nums[i]);
backtrack(current);
current.pop();
used[i] = false;
}
}
backtrack([]);
return result;current.length === nums.length — a full-length path is a leaf. Push [...current] (spread!) and return.[1, 2, 3] produce?result.push(current) instead of result.push([...current]). What happens?permute([1, 2]). What is the correct output?