What Could Go Wrong?

Your cyclic sort works beautifully on permutations — every value unique, every home address occupied by exactly one rightful owner. But real interview problems aren't that clean. Consider this array:

1
const nums = [1, 3, 2, 3, 5];
2
//   index:   0  1  2  3  4

Walk through it mentally. Index 0 holds 1 — already home. Index 1 holds 3 — its home is index 2. Swap nums[1] and nums[2]: now the array is [1, 2, 3, 3, 5]. Index 1 now holds 2 — home. Index 2 holds 3 — home. Move on to index 3.

Index 3 holds 3. Its home address is 3 - 1 = 2. So we check nums[2]... which is also 3. The swap executes: we exchange nums[3] with nums[2]. But both positions hold the same value. Nothing changes. And our while loop condition — nums[i] !== i + 1 — is still true. Index 3 still holds 3, which isn't 4. So we swap again. Same result. And again. And again. We're trapped in an infinite loop, endlessly swapping identical values while the condition never becomes false. The algorithm that was O(n) just became O(infinity).

Duplicates don't just cause wrong answers — they cause hangs. Your mailroom has two letters addressed to the same house, and the carrier keeps shuffling them back and forth forever. We need a guard.

The Duplicate Trap

Indices 0-2 are settled. Pointer i is at index 3 where nums[3]=3 and home=2.
1
0
3
1
2
2
i
3
3
5
4

What happens if we swap nums[3] with nums[2]?

One Guard, Two Saves

The fix is a single condition change, but it does double duty:

1
for (let i = 0; i < n; i++) {
2
  while (nums[i] !== i + 1) {
3
    const home = nums[i] - 1;
4
    // The guard: stop if the home already has the right value
5
    if (nums[i] === nums[home]) break;  // <-- new
6
    [nums[i], nums[home]] = [nums[home], nums[i]];
7
  }
8
}

Read that guard carefully: nums[i] === nums[home]. When is this true? Two cases. First, when the value at index i is already in its correct position — nums[i] equals nums[home] because i is home. That's the “already placed” case. Second, when a duplicate occupies the home — some other copy of the value is already sitting where we'd want to send this one. That's the “duplicate detected” case.

One condition, two saves. It prevents infinite loops from duplicates and skips unnecessary swaps for values already home. The guard is cheap — one comparison per iteration — and it preserves our O(n) budget because each break means we stop swapping and advance i.

Now here's the interesting part: after this modified sort finishes, the array has a telltale signature. Every position either holds its “correct” value... or it holds an imposter — a duplicate that couldn't be placed. That signature is about to become very useful.