283. Move Zeroes

Shift every 0 to the end of the array in place while keeping the non-zeroes in their original order. A slow write pointer packs the non-zeroes to the front; the zeroes fall out the back for free.

EasyTwo PointersIn-place WriteTypeScript

PROBLEM What we're solving

Push every 0 to the back of nums, in place, without disturbing the order of the non-zero values. Example: nums=[0,1,0,3,12] becomes [1,3,12,0,0] — the non-zeroes 1,3,12 stay in order and the two zeroes drift to the end.

KEY IDEA Pack the keepers, zeroes fall out the back

Insight → don't think about moving zeroes — think about compacting the non-zeroes. Keep a slow pointer at the next empty write slot. Sweep fast across the array; every time it lands on a non-zero, swap that value into slow and bump slow. Once the keepers are packed at the front in order, the tail is automatically all zeroes.

RECIPE Slow writes, fast scans

  • 0 · Two pointers, same end. Start slow=0 and fast=0. Both walk left→right; slow never outruns fast.
  • 1 · Scan with fast. If nums[fast]===0, do nothing — why: leave the hole so a later non-zero can overwrite it.
  • 2 · Swap on non-zero. If nums[fast]!==0, swap it into nums[slow] then slow++ why: this places the next keeper at the front and sends any trapped zero forward.
  • 3 · Finish. When fast reaches the end, the first slow cells are the non-zeroes in order and everything after is zeroes.
Classic confusion → a tempting shortcut is to just copy nums[slow]=nums[fast] for non-zeroes and then fill the tail with zeroes in a second pass. That works, but you must then zero out indices slow…n-1 — forget that and stale values linger. The swap version avoids the second pass because each swap carries a zero backward.

COST Complexity & alternatives

Copy out non-zeroes, copy back
O(n) time · O(n) space
Simple, but allocates a second array — violates "in place".
Slow/fast write pointer
O(n) time · O(1) space
One pass, swaps in place, no extra array.

Why O(1) space

The two pointers are the only extra storage; all rearrangement happens inside the original array via swaps. Each element is examined once, so it is a single linear pass with at most n swaps.

Pattern transfer → the slow-write pointer is the same engine behind Remove Element, Remove Duplicates from Sorted Array, and Sort Colors— any "compact items that satisfy a predicate to the front, in place" task.

RUN IT Slow writes, fast scans — zeroes drift right

step 0 / 6
STARTStart. slow marks the next slot for a non-zero; fastscans left → right.
1function moveZeroes(nums: number[]): void {
2 let slow = 0; // next slot for a non-zero
3 for (let fast = 0; fast < nums.length; fast++) {
4 if (nums[fast] !== 0) { // found a non-zero to keep
5 [nums[slow], nums[fast]] = [nums[fast], nums[slow]];
6 slow++; // advance the write pointer
7 }
8 }
9}
nums0slow·fast110233124
State
0
slow
0
fast
slow (write slot)fast (scanner)just swappedplaced non-zero
slowfast

TYPESCRIPT The solution, annotated

moveZeroes.ts
function moveZeroes(nums: number[]): void {
  let slow = 0;                       // next slot for a non-zero
  for (let fast = 0; fast < nums.length; fast++) {
    if (nums[fast] !== 0) {           // found a non-zero to keep
      [nums[slow], nums[fast]] = [nums[fast], nums[slow]];
      slow++;                         // advance the write pointer
    }
  }
}

Reading it block by block

Line 2 — the write pointer. slow tracks where the next non-zero should land. Everything before slow is already a packed, ordered non-zero.
Line 3 — scan with fast. fast visits every index once, looking for the next value worth keeping.
Lines 4–6 — swap and advance. When nums[fast] is non-zero, swap it into nums[slow]. If slow===fast the swap is a no-op; otherwise nums[slow] held a zero, which now rides forward to index fast. Then slow++ claims the next slot.
Zeroes need no handling. When nums[fast]===0 the loop simply moves on, slow unchanged — the zero stays put as a hole to be overwritten or pushed back by a later swap.
Complexity → O(n) time — one pass, each element touched once with at most one swap. O(1) extra space — only the two index variables; the array is mutated in place.

INTERVIEWFollow-ups they'll ask

  • "Minimize the number of writes?" Skip the swap when slow===fast (the element is already in place); only swap when slow<fast.
  • "Move zeroes to the front instead?" Walk both pointers from the right end and compact non-zeroes toward the back.
  • "Move all instances of value k?" Replace the !== 0 test with !== k — this is exactly Remove Element.
  • "Why not sort?" Sorting is O(n log n) and would scramble the relative order of the non-zeroes, which the problem requires preserving.

OPTIMAL Two Pointers

moveZeroes.ts
function moveZeroes(nums: number[]): void {
  let slow = 0;                       // next slot for a non-zero
  for (let fast = 0; fast < nums.length; fast++) {
    if (nums[fast] !== 0) {           // found a non-zero to keep
      [nums[slow], nums[fast]] = [nums[fast], nums[slow]];
      slow++;                         // advance the write pointer
    }
  }
}
Complexity → O(n) time — one pass, each element touched once with at most one swap. O(1) extra space — only the two index variables; the array is mutated in place.

ALT 1 Overwrite then zero-fill (two passes)

O(n) time · O(1) space

Copy each non-zero forward with the same slow cursor, then make a second pass filling slow…n-1 with zeroes.

approach-2.ts
function moveZeroes(nums: number[]): void {
  let slow = 0;
  for (let fast = 0; fast < nums.length; fast++) {
    if (nums[fast] !== 0) nums[slow++] = nums[fast];
  }
  while (slow < nums.length) nums[slow++] = 0;
}
Note → Same complexity and arguably clearer, but it requires the explicit zero-fill loop. The swap version folds that work into the first pass.

ALT 2 Stable copy into a fresh array

O(n) time · O(n) space

The most obvious idea — and the one to avoid because it allocates.

approach-3.ts
function moveZeroes(nums: number[]): void {
  const out: number[] = [];
  for (const x of nums) if (x !== 0) out.push(x);
  while (out.length < nums.length) out.push(0);
  for (let i = 0; i < nums.length; i++) nums[i] = out[i];
}
Note → Correct but uses O(n) auxiliary space, which violates the in-place requirement of the problem.

MNEMONIC The one-liner

"Slow holds the pen, fast does the reading — write a keeper, then step the pen."

TRIGGERS When you see ___ → reach for ___

"in place, preserve order"slow write pointer + swap
compact items to the frontswap into slow, slow++
move/remove a target valuepredicate on nums[fast]
partition by a conditiontwo pointers, same direction

SKELETON The reusable shape

skeleton.ts
let slow = 0;
for (let fast = 0; fast < nums.length; fast++) {
  if (nums[fast] !== 0) {
    [nums[slow], nums[fast]] = [nums[fast], nums[slow]];
    slow++;
  }
}

FLASHCARDS Tap to flip

What does the slow pointer represent?
The next index to write a non-zero (keeper) into. Everything before it is already packed and ordered.
tap to flip
1 / 6
Answer to reveal explanations. No penalty for retries.Score: 0 / 9
QUESTION 01
What does the slow pointer track?
QUESTION 02
Optimal time and space complexity?
QUESTION 03
When fast lands on a zero, what happens?
QUESTION 04
For nums=[0,1,0,3,12], the final array is:
QUESTION 05
Why is the relative order of non-zeroes preserved?
QUESTION 06
A swap actually changes two values only when:
QUESTION 07
Which related problem uses the same slow-write-pointer engine?
QUESTION 08
#283 · Move ZeroesPush every zero to the end while keeping the non-zeroes in order using a slow insert pointer: swap each non-zero into the next open slot in one O(n) pass with O(1) extra space.Which algorithmic approach does this primarily use?
QUESTION 09
#283 · Move ZeroesPush every zero to the end while keeping the non-zeroes in order using a slow insert pointer: swap each non-zero into the next open slot in one O(n) pass with O(1) extra space.Which implementation correctly solves it?