41. First Missing Positive

Find the smallest positive integer missing from an unsorted array in O(n) time and O(1) extra space. The trick: turn the array into its own hash table by sending every value v to slot v − 1, then scan for the first slot that's wrong.

HardIndex as HashCyclic SortTypeScript

PROBLEM What we're solving

Given an unsorted array, return the smallest positive integer (1, 2, 3, …) that does not appear in it. Take nums = [3,4,-1,1]. The positives present are {1, 3, 4}; 2 is missing, so the answer is 2. The catch is the constraint: do it in O(n) time and O(1) extra space, which rules out a hash set or sorting.

KEY IDEA The array is the hash table

Index-as-hash → the answer for an array of length n is always in [1, n+1]. So value v belongs at index v − 1. Permute the array in place until every in-range value sits in its home slot; then the first index i whose value isn't i + 1 reveals the missing number i + 1.

Why is the answer at most n + 1? With only n slots, the best case is that 1..n are all present, leaving n + 1. Negatives, zeros, and values > n are irrelevant — they can never be the answer, so we simply ignore them.

RECIPE Sort by cycles, then scan

  • 1 · Walk i from 0 to n−1. For each position, repeatedly place its value home.
  • 2 · Swap into place while nums[i] is in [1, n]AND its home slot doesn't already hold it: send nums[i] to index nums[i] − 1.
  • 3 · Stop swapping when nums[i] is out of range or its home already holds the right value (duplicate) — then advance i.
  • 4 · Scan for the first index where nums[i] !== i + 1; return i + 1. If none, return n + 1.
Classic confusion → the inner loop must be a while, not an if, and you must not advance i after a swap. A single swap drops a new value into nums[i] that may itself need placing. The duplicate guard nums[nums[i] − 1] !== nums[i] is what stops an infinite swap loop when the home slot already holds the same value.

COST Complexity & alternatives

Hash set / sort
O(n) space
Easy, but breaks the O(1)-space constraint (or O(n log n) time).
Cyclic sort in place
O(n) / O(1)
O(n) time, O(1) extra space. The intended Hard answer.

Why is the swap loop still O(n)?

Each swap puts at least one value into its final home, and a value never leaves a correct home. So across the whole outer loop there are at most n swaps total — the nested while is amortized O(1) per index, keeping the phase O(n).

Pattern transfer → the index-as-hash / cyclic-sort move also cracks Missing Number, Find the Duplicate Number, Find All Numbers Disappeared in an Array, and Set Mismatch— any "values are 1..n, find the odd one in O(1) space" problem.

RUN IT Send each value home (v → slot v−1), then scan for the gap

step 0 / 17
STARTLength n = 4. The answer must be in [1, 5]. Phase 1: send each value v to slot v-1.
1function firstMissingPositive(nums: number[]): number {
2 const n = nums.length;
3
4 // Phase 1 — place each value v in slot v-1 (cyclic sort).
5 for (let i = 0; i < n; i++) {
6 // Only swap when nums[i] is a useful, out-of-place value 1..n.
7 while (
8 nums[i] >= 1 &&
9 nums[i] <= n &&
10 nums[nums[i] - 1] !== nums[i]
11 ) {
12 const target = nums[i] - 1; // where nums[i] belongs
13 [nums[i], nums[target]] = [nums[target], nums[i]];
14 }
15 }
16
17 // Phase 2 — first index that doesn't hold i+1 is the answer.
18 for (let i = 0; i < n; i++) {
19 if (nums[i] !== i + 1) return i + 1;
20 }
21
22 // Every slot 1..n is filled, so the answer is n+1.
23 return n + 1;
24}
nums =3041-1213
State
4
n
0
i
3
nums[i]
home (v-1)
1 · sort
phase
current index ihome slot / candidatecorrectly placed (i+1)the gap (answer)
slowfast

TYPESCRIPT The solution, annotated

firstMissingPositive.ts
function firstMissingPositive(nums: number[]): number {
  const n = nums.length;

  // Phase 1 — place each value v in slot v-1 (cyclic sort).
  for (let i = 0; i < n; i++) {
    // Only swap when nums[i] is a useful, out-of-place value 1..n.
    while (
      nums[i] >= 1 &&
      nums[i] <= n &&
      nums[nums[i] - 1] !== nums[i]
    ) {
      const target = nums[i] - 1;        // where nums[i] belongs
      [nums[i], nums[target]] = [nums[target], nums[i]];
    }
  }

  // Phase 2 — first index that doesn't hold i+1 is the answer.
  for (let i = 0; i < n; i++) {
    if (nums[i] !== i + 1) return i + 1;
  }

  // Every slot 1..n is filled, so the answer is n+1.
  return n + 1;
}

Reading it block by block

Line 2 — the bound. n = nums.length. Every value we care about lives in [1, n], and the final answer is somewhere in [1, n + 1].
Lines 5–16 — phase 1, cyclic sort. For each index we try to put its value into its home slot nums[i] - 1. The while keeps placing whatever lands in nums[i]until it can't place anymore.
Lines 8–12 — the swap guard. Three conditions must hold to swap: nums[i] >= 1, nums[i] <= n (in range), and nums[nums[i] - 1] !== nums[i](its home doesn't already hold it). That last check skips duplicates and prevents an infinite loop.
Lines 13–14 — the swap. Send nums[i] to its home index target = nums[i] - 1. The destructuring swap exchanges the two slots in one line; we do not advance i, because a fresh value just arrived at nums[i].
Lines 19–21 — phase 2, the scan. After sorting, slot i should hold i + 1. The first index where it doesn't is the gap: return i + 1.
Line 24 — all present. If every slot is correct, 1..n are all there, so the smallest missing positive is n + 1.
Complexity → Phase 1 does at most n swaps total (each places a value in its final home), so it is O(n) amortized despite the inner while. Phase 2 is a single O(n) scan. Total O(n) time, O(1) extra space — we mutate nums in place.

INTERVIEWFollow-ups they'll ask

  • "Why is the answer guaranteed to be in [1, n+1]?" With n slots, at most n distinct positives fit; if 1..n all appear, the gap is n + 1.
  • "Why a while loop, not an if?" A swap brings a new, possibly out-of-place value into nums[i]; the while keeps placing until nums[i] is settled before i advances.
  • "What stops the swaps from looping forever?" The guard nums[nums[i] − 1] !== nums[i] — once a home already holds the value (a duplicate), we stop.
  • "What if you can't modify the input?" Then you need O(n) extra space (a boolean/hash array) or you must clone the array first.
  • "Alternative without swaps?" Sign-marking: use the sign of nums[v − 1] as a present/absent bit, after first sanitizing non-positives — also O(n)/O(1).

OPTIMAL Index as Hash

firstMissingPositive.ts
function firstMissingPositive(nums: number[]): number {
  const n = nums.length;

  // Phase 1 — place each value v in slot v-1 (cyclic sort).
  for (let i = 0; i < n; i++) {
    // Only swap when nums[i] is a useful, out-of-place value 1..n.
    while (
      nums[i] >= 1 &&
      nums[i] <= n &&
      nums[nums[i] - 1] !== nums[i]
    ) {
      const target = nums[i] - 1;        // where nums[i] belongs
      [nums[i], nums[target]] = [nums[target], nums[i]];
    }
  }

  // Phase 2 — first index that doesn't hold i+1 is the answer.
  for (let i = 0; i < n; i++) {
    if (nums[i] !== i + 1) return i + 1;
  }

  // Every slot 1..n is filled, so the answer is n+1.
  return n + 1;
}
Complexity → Phase 1 does at most n swaps total (each places a value in its final home), so it is O(n) amortized despite the inner while. Phase 2 is a single O(n) scan. Total O(n) time, O(1) extra space — we mutate nums in place.

ALT 1 Sort, then scan for the first gap

O(n log n) time · O(1) extra space (ignoring sort)

The most obvious correct approach: sort the array, then walk it looking for the first positive integer that's skipped.

approach-2.ts
function firstMissingPositive(nums: number[]): number {
  nums.sort((a, b) => a - b);

  // The next positive integer we expect to see.
  let expected = 1;
  for (const v of nums) {
    // Skip non-positives and duplicates of the current expected value.
    if (v < expected) continue;
    if (v === expected) {
      expected++;            // found it — advance to the next
    } else {
      // v > expected, so 'expected' was skipped: that's the gap.
      return expected;
    }
  }

  // Saw 1, 2, ..., expected-1 with no gap; answer is the next one.
  return expected;
}
Note → Simple and easy to get right under pressure, but the O(n log n) sort misses the O(n) bar. Mutating the input via sort also destroys the original order.

ALT 2 Hash set membership

O(n) time · O(n) space

Dump every value into a set, then probe 1, 2, 3, … until one is missing. Hits O(n) time but spends O(n) space.

approach-3.ts
function firstMissingPositive(nums: number[]): number {
  const seen = new Set<number>();
  for (const v of nums) {
    if (v > 0) seen.add(v);   // only positives can ever be the answer
  }

  // The answer is in [1, n+1]; probe in order for the first miss.
  let candidate = 1;
  while (seen.has(candidate)) {
    candidate++;
  }
  return candidate;
}
Note → The clearest O(n)-time solution and it leaves the input untouched, but the auxiliary set breaks the O(1)-space constraint — the whole point of the Hard variant.

ALT 3 Negation marking (sign-trick)

O(n) time · O(1) extra space

A swap-free sibling of cyclic sort: use the sign of nums[v − 1] as a present/absent bit after cleaning out-of-range values.

approach-4.ts
function firstMissingPositive(nums: number[]): number {
  const n = nums.length;

  // Step 1 — neutralize values that can't be the answer.
  // Replace anything <= 0 or > n with n+1 (a harmless out-of-range value).
  for (let i = 0; i < n; i++) {
    if (nums[i] <= 0 || nums[i] > n) nums[i] = n + 1;
  }

  // Step 2 — for each in-range value v, mark slot v-1 negative as "present".
  // Use the absolute value, since a slot may already be negated.
  for (let i = 0; i < n; i++) {
    const v = Math.abs(nums[i]);
    if (v >= 1 && v <= n) {
      const idx = v - 1;
      if (nums[idx] > 0) nums[idx] = -nums[idx];
    }
  }

  // Step 3 — first slot still positive means v=i+1 was never seen.
  for (let i = 0; i < n; i++) {
    if (nums[i] > 0) return i + 1;
  }

  // All of 1..n were marked present.
  return n + 1;
}
Note → Same O(n)/O(1) bounds as cyclic sort and arguably easier to reason about (no nested while), but it requires the sanitizing pass first and only works because every relevant value lands in [1, n].

MNEMONIC The one-liner

"Send each value home (v → slot v−1), then point at the first empty chair."

TRIGGERS When you see ___ → reach for ___

"smallest missing positive"index-as-hash, answer in [1, n+1]
values are 1..n, O(1) spacecyclic sort (swap to slot v−1)
swap brings new value to nums[i]while loop, do not advance i
home already holds valueduplicate guard, stop swapping

SKELETON The reusable shape

skeleton.ts
const n = nums.length;
for (let i = 0; i < n; i++) {
  while (nums[i] >= 1 && nums[i] <= n && nums[nums[i] - 1] !== nums[i]) {
    const t = nums[i] - 1;
    [nums[i], nums[t]] = [nums[t], nums[i]];
  }
}
for (let i = 0; i < n; i++) {
  if (nums[i] !== i + 1) return i + 1;
}
return n + 1;

FLASHCARDS Tap to flip

Why is the answer always in [1, n+1]?
An array of length n can hold at most n distinct positives; if 1..n all appear, the gap is n + 1.
tap to flip
1 / 7
Answer to reveal explanations. No penalty for retries.Score: 0 / 9
QUESTION 01
For nums = [3,4,-1,1], the first missing positive is:
QUESTION 02
What is the optimal time and space?
QUESTION 03
During phase 1, which value belongs at index i once sorted?
QUESTION 04
Why must the inner placement be a while loop instead of an if?
QUESTION 05
What stops the swaps from looping forever on duplicates like [1,1]?
QUESTION 06
Which values are simply ignored during the cyclic sort?
QUESTION 07
For nums = [1,2,3], the function returns:
QUESTION 08
#41 · First Missing PositiveFind the smallest missing positive integer in O(n) time and O(1) extra space by using the array itself as a hash table: place each value v at index v-1 via cyclic sort, then scan for the first index that does not hold i+1.Which algorithmic approach does this primarily use?
QUESTION 09
#41 · First Missing PositiveFind the smallest missing positive integer in O(n) time and O(1) extra space by using the array itself as a hash table: place each value v at index v-1 via cyclic sort, then scan for the first index that does not hold i+1.Which implementation correctly solves it?