The backtracking template

Choose, explore, un-choose — one skeleton that solves permutations, subsets and combinations.

Backtracking is a depth-first walk over the space of partial solutions. At each step you make a choice, explore everything that follows from it, then undo the choice and try the next one.

The skeleton never changes:

function backtrack(state, choices, results) {
  if (isComplete(state)) {
    results.push([...state]);        // copy — state keeps mutating
    return;
  }

  for (const choice of choices) {
    if (!isValid(state, choice)) continue;

    state.push(choice);              // choose
    backtrack(state, next(choices, choice), results);
    state.pop();                     // un-choose
  }
}

The state.pop() is the backtracking. Forget it and every branch pollutes the next.

Permutations

function permutations(items) {
  const results = [];
  const used = new Array(items.length).fill(false);
  const current = [];

  const walk = () => {
    if (current.length === items.length) { results.push([...current]); return; }

    for (let i = 0; i < items.length; i++) {
      if (used[i]) continue;

      used[i] = true;  current.push(items[i]);
      walk();
      current.pop();   used[i] = false;
    }
  };

  walk();
  return results;
}

O(n × n!) — there are n! permutations and copying each costs n.

Subsets

function subsets(items) {
  const results = [];
  const current = [];

  const walk = (start) => {
    results.push([...current]);              // every node is an answer

    for (let i = start; i < items.length; i++) {
      current.push(items[i]);
      walk(i + 1);                           // i + 1 prevents reuse and duplicates
      current.pop();
    }
  };

  walk(0);
  return results;
}

O(n × 2ⁿ). The start parameter is what separates combinations (order does not matter) from permutations (it does).

The copy that catches everyone

results.push(current) pushes a reference to an array you are about to mutate — every result ends up identical and empty. Always [...current].

Handling duplicates

Sort the input, then skip a choice equal to its predecessor at the same level:

if (i > start && items[i] === items[i - 1]) continue;

That one line is the standard answer for "subsets II" and "permutations II".