Move Zeroes: Two Pointers for In-Place Partitioning
LeetCode Move Zeroes (problem 283) asks you to move all zeroes in an integer array to the end while keeping the relative order of non-zero elements intact. You must do it in-place without making a copy of the array.
Given nums = [0, 1, 0, 3, 12], the result should be [1, 3, 12, 0, 0]. The non-zero values 1, 3, and 12 keep their original left-to-right order. The zeroes slide to the back.
Why this problem matters
Move Zeroes is a fundamental two-pointer partitioning problem. The "slow writer, fast reader" technique you use here shows up in Remove Duplicates (LeetCode 26), Remove Element (LeetCode 27), and many other in-place array problems. Once you internalize this pattern, you can adapt it to any problem that asks you to rearrange elements in a single pass without extra memory.
The twist compared to Remove Element is that you must preserve relative order and you cannot just ignore the tail of the array. Every position matters in the final output.
The approach: write pointer and read pointer
Keep a write pointer that tracks where the next non-zero element should go. Scan through the array with a read pointer. Every time read finds a non-zero value, swap it into the write position and advance write.
- read visits every index from 0 to
n - 1 - write only advances when a non-zero is placed
- Swapping ensures the zero that was at write moves to the read position, where it will eventually be pushed further right by future swaps
def move_zeroes(nums):
write = 0
for read in range(len(nums)):
if nums[read] != 0:
nums[write], nums[read] = nums[read], nums[write]
write += 1
Step-by-step walkthrough
Start: read sees nums[0] = 0. It is zero, so skip it.
nums[read] is 0. Only advance read. Write stays at 0.
Step 1: read sees nums[1] = 1. Non-zero, swap with write position.
Swap nums[0] and nums[1]. Now nums[0] = 1. Advance both pointers.
Step 2: read sees nums[2] = 0. Zero, skip it.
nums[read] is 0. Only advance read. Write stays at 1.
Step 3: read sees nums[3] = 3. Non-zero, swap with write position.
Swap nums[1] and nums[3]. Now nums[1] = 3. Advance both pointers.
Step 4: read sees nums[4] = 12. Non-zero, swap with write position.
Swap nums[2] and nums[4]. Now nums[2] = 12. Advance both pointers.
Done: read passed the end. All zeroes are at the back.
Result: [1, 3, 12, 0, 0]. Non-zero elements maintain their original relative order.
Notice how write stays behind whenever we encounter a zero. When read finds a non-zero value, the swap places it in the correct position at write. After the loop finishes, everything from write onward is guaranteed to be zero because every non-zero has already been moved left.
Why swap instead of overwrite?
You might wonder why we swap instead of just copying the non-zero value to the write position and then filling zeroes at the end. Both approaches work, but swapping has a nice property: it preserves the zero at the read position without needing a second pass. Each swap moves a zero to the right and a non-zero to the left in one operation. No cleanup needed.
The overwrite approach requires two passes: first copy non-zeroes forward, then fill the remaining positions with 0. Swapping does it all in one pass, which is slightly cleaner even though the time complexity is the same.
Complexity analysis
| Metric | Value |
|---|---|
| Time | O(n) |
| Space | O(1) |
| Swaps | At most n |
Every element is read exactly once. The number of swaps equals the number of non-zero elements, which is at most n. No auxiliary data structures are used.
The building blocks
This problem is built on the two-pointer partitioning pattern. The same concept appears whenever you need to separate elements into two groups in-place:
- Remove Element (LeetCode 27) uses the same write/read structure, but copies instead of swapping since you do not care about the tail.
- Dutch National Flag / Sort Colors (LeetCode 75) extends the idea to three-way partitioning with three pointers instead of two.
- Remove Duplicates from Sorted Array (LeetCode 26) uses a write pointer that skips duplicates instead of zeroes.
The mental model is always the same: write marks the boundary of "processed good elements" and read scans ahead looking for the next element to place.
Edge cases to watch for
- No zeroes (nothing moves, write tracks read exactly and every swap is a no-op since
write == read) - All zeroes (write never advances, every element stays where it is)
- Single element (loop runs once, result is trivially correct)
- Zeroes only at the end already (swaps are no-ops since write and read stay in sync until read passes the non-zero section)
From understanding to recall
This problem is easy to understand when you see the walkthrough. The real challenge is reproducing the solution from scratch during an interview without hesitating on pointer placement. Practice writing it on a blank screen. Once you can produce the loop confidently, space out your reviews over days and weeks. That is how the pattern moves from short-term memory into permanent muscle memory that you can apply to harder partitioning problems without thinking.
Related posts
- Remove Element - Same two-pointer technique, just removing a target value
- Remove Duplicates from Sorted Array - Write pointer skips duplicates instead of zeroes
- Sort Colors - A more complex partitioning problem using the same pointer ideas