Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Move all zeroes to the end in place while keeping the non-zero order. The naive answer copies non-zeros into a buffer then pads zeros; the canonical answer keeps a write pointer for the next non-zero slot and swaps each non-zero forward — one pass, O(1) space, minimal writes.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you can do a stable in-place partition with a single write pointer and minimal writes, instead of using an extra array or doing redundant write passes.
Where candidates slip
Breaking the relative order of non-zeros, doing two passes (overwrite then zero-fill) when one swap pass suffices, or writing every element even when most are already in place.
How to stand out
Note the write pointer is the boundary of the packed region, explain why swapping preserves order AND removes the zero-fill pass, and mention the all-non-zero case swaps each element with itself (harmless).
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewMove all zeroes to the endPreserve non-zero order?NoYesExplore the full treeCollapse the tree
nums = [0,1,0,3,12]How to read it:write is the next free non-zero slot. Each non-zero is swapped into it; zeros are skipped and drift to the back.
- Frame 1
w=0, r=0nums[0]=0 → skip, write stays 0
w↓r↓0[0]1[1]0[2]3[3]12[4] - Frame 2
w=0, r=1nums[1]=1 != 0 → swap(0,1) → write=1
1[0]w↓r↓0[1]0[2]3[3]12[4] - Frame 3
w=1, r=3nums[3]=3 != 0 → swap(1,3) → write=2
1[0]3[1]w↓0[2]r↓0[3]12[4] - Frame 4
w=2, r=4nums[4]=12 != 0 → swap(2,4) → write=3
1[0]3[1]12[2]w↓0[3]r↓0[4] - Frame 5
donenon-zeros [1,3,12] packed; zeros at tail
1[0]3[1]12[2]w↓0[3]0[4]
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Copy Non-Zeros to a Buffer (Naive)
1Hint 1Explore hintHide hint
Ignore the in-place rule for a moment: how would you produce [non-zeros..., zeros...] in a fresh array?
2Hint 2Explore hintHide hint
Scan nums, appending every non-zero to a new list. Then append enough zeros to reach length n.
3Hint 3Explore hintHide hint
Copy that list back into nums. It's correct but uses an O(n) buffer — exactly what the problem says not to do.
A · Core idea
Build the answer in a separate list — first all the non-zero values in their original order, then enough trailing zeros to refill the length — and copy it back into nums.
Why this is the natural first attempt. The target shape is literally "non-zeros, then zeros". The most direct way to produce that shape is to gather the non-zeros, count how many zeros are missing, and append them. There's no overwrite hazard because we write into fresh memory, so it's trivially correct and order-preserving.
Why it's order-preserving for free. Appending non-zeros during a left-to-right scan visits them in their original order, so the buffer holds them stably. The zeros are indistinguishable from each other, so 'maintaining their order' is vacuous — we just pad with the right count.
Why it violates the spec. The problem says "do this in place without making a copy". This solution makes exactly that copy: an O(n) auxiliary list. Functionally fine, judged-correct on many platforms, but it sidesteps the skill the question is testing — rearranging within the original array.
Why we still bring it up. It pins down the goal shape and the stability requirement in the simplest possible terms. The optimal solution is really just this idea with the buffer eliminated: instead of writing non-zeros into a new list, write them into the front of nums itself with a write pointer, and let swaps carry the zeros to the back.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
nums = [0,1,0,3,12]scan
collect non-zeros 1, 3, 12
buf = [1, 3, 12][1,3,12,0,0]C · Code with teaching comments
Reference implementation previewpublic void moveZeroes(int[] nums) { int[] buf = new int[nums.length]; int idx = 0; for (int x : nums) { if (x != 0) {Explore the full codeHide the full code
D · Why it works and complexityO(n) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
The naive version makes the target shape obvious — non-zeros first, zeros after. The only flaw is the extra array; replacing it with an in-place write pointer over nums removes the O(n) space while keeping the exact same logic.
ComplexityO(n) time, O(n) space — One pass to collect non-zeros and one to copy back → O(n) time. The result list holds up to n elements → O(n) space.
E · Edge cases and pitfalls5nums = [0,0,0] — No non-zeros collected; buffer is all zeros; nums unchanged.Explore edge casesHide edge cases
Edge cases handled
nums = [0,0,0]No non-zeros collected; buffer is all zeros; nums unchanged.nums = [1,2,3]All collected, zero padding count is 0; nums unchanged.
Pitfalls
- Allocating a buffer violates the in-place requirement — baseline only.
- Forgetting to pad zeros to the original length leaves nums too short (Python) or stale (Java).
- Counting zeros wrong when padding.
Approach 2 of 2 · Optimal
Write Pointer with Swap (Optimal)
1Hint 1Explore hintHide hint
You don't need a second array — the front of nums can be the output region. What invariant lets you safely place non-zeros there as you scan?
2Hint 2Explore hintHide hint
Keep write = index of the next slot that should receive a non-zero. Scan with read; everything in nums[0..write-1] is the packed non-zero prefix.
3Hint 3Explore hintHide hint
When nums[read] is non-zero, swap it into nums[write] and increment write. The swap sends whatever was at write (a zero, once we've passed one) back to read's position. Zeros never advance write, so they pool at the end.
A · Core idea
Maintain a write index marking where the next non-zero belongs. Scan with read. Whenever nums[read] is non-zero, swap it into nums[write] and advance write. Zeros are passed over, so write lags behind read and the gap between them is exactly the zeros seen so far.
The invariant. At all times nums[0..write-1] holds the non-zero values encountered so far, in their original order, and nums[write..read-1] holds only zeros. write <= read always, so the slot we swap into has already been read — we never lose data. When the scan finishes, the non-zeros fill the front stably and every zero has been pushed past write to the tail. This is the same write-head-trails-read-head guarantee behind every stable in-place partition.
Why swapping (not just assigning) is the elegant move. A two-pass alternative overwrites nums[write++] = nums[read] for each non-zero, then loops again to zero out nums[write..n-1]. The swap fuses those passes into one: when write is behind read, the slot at write is guaranteed to hold a zero (every non-zero before read was already pulled forward), so swapping deposits the non-zero up front and carries that zero back to read's position in a single operation. No separate zero-fill pass is needed.
Why order is preserved. We scan left to right and only ever move a non-zero forward into the lowest available slot. Two non-zeros a before b in the input are written in that same sequence — a claims a smaller write than b — so their relative order is intact. Stability falls out of always filling the front-most free slot.
Why writes are minimal. write advances only on non-zeros, so runs of zeros do zero work. And when write == read (no zeros seen yet), the swap is nums[i] with itself — a harmless no-op you can optionally guard with if (write != read) to avoid redundant writes. Compared to the unconditional two-pass overwrite, this touches each element at most once.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
nums = [0,1,0,3,12]Array state
skip zero
r=0: 0 → write stays 0
[1,3,12,0,0]C · Code with teaching comments
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
One write pointer that advances only on 'keep' elements is the canonical stable partition. The swap does double duty — it places the non-zero and evicts a zero in the same step — which is why a single pass with no zero-fill suffices. Recognise this shape and Remove Element, Sort Colors, and quicksort's partition all become variations on it.
ComplexityO(n) time, O(1) space — read makes one pass (O(n)); each step is O(1). Only two index variables are used → O(1) space. Swaps occur once per non-zero, so writes are minimal — runs of zeros cost nothing.
E · Edge cases and pitfalls7nums = [0] — Single zero is skipped, write stays 0, array unchanged.Explore edge casesHide edge cases
Edge cases handled
nums = [0]Single zero is skipped, write stays 0, array unchanged.nums = [1,2,3]Every element swaps with itself (write == read), order and values unchanged.nums = [0,0,1]Two zeros skipped, then 1 swaps from index 2 to index 0 → [1,0,0].
Pitfalls
- Doing two passes (overwrite then zero-fill) when one swap pass suffices.
- Advancing write on zeros — breaks the packing and the ordering.
- Assigning nums[write] = nums[read] without swapping, then forgetting to zero out the tail.
- Reordering non-zeros (e.g. swapping from both ends) — violates the stability requirement.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic void moveZeroes(int[] nums) { int write = 0; for (int read = 0; read < nums.length; read++) { if (nums[read] != 0) { int tmp = nums[write];Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Overwrite without restoring zeros
Copying nums[write++] = nums[read] for non-zeros packs the front but leaves stale values in the tail. Without a second pass to zero-fill, the array is wrong.
Explore the code comparisonHide the code comparison
Overwrite without restoring zeros
Copying nums[write++] = nums[read] for non-zeros packs the front but leaves stale values in the tail. Without a second pass to zero-fill, the array is wrong.
Don't write this
int write = 0;
for (int read = 0; read < nums.length; read++) {
if (nums[read] != 0) {
nums[write++] = nums[read]; // tail still holds old values
}
}
// missing: fill nums[write..n-1] with 0Write this instead
int write = 0;
for (int read = 0; read < nums.length; read++) {
if (nums[read] != 0) {
int tmp = nums[write];
nums[write] = nums[read];
nums[read] = tmp;
write++;
}
}2Swapping from both ends breaks order
Partitioning by swapping a zero at the front with a non-zero at the back is fewer writes, but it scrambles the relative order of non-zeros, which this problem forbids.
Explore the code comparisonHide the code comparison
Swapping from both ends breaks order
Partitioning by swapping a zero at the front with a non-zero at the back is fewer writes, but it scrambles the relative order of non-zeros, which this problem forbids.
Don't write this
lo, hi = 0, len(nums) - 1
while lo < hi:
if nums[lo] == 0:
nums[lo], nums[hi] = nums[hi], nums[lo] # reorders non-zeros
hi -= 1
else:
lo += 1Write this instead
write = 0
for read in range(len(nums)):
if nums[read] != 0:
nums[write], nums[read] = nums[read], nums[write]
write += 13Advancing write on zeros
If write moves forward for zeros too, non-zeros land in wrong slots and zeros get interleaved instead of pooling at the end.
Explore the code comparisonHide the code comparison
Advancing write on zeros
If write moves forward for zeros too, non-zeros land in wrong slots and zeros get interleaved instead of pooling at the end.
Don't write this
write = 0
for read in range(len(nums)):
nums[write], nums[read] = nums[read], nums[write]
write += 1 # advances even for zerosWrite this instead
write = 0
for read in range(len(nums)):
if nums[read] != 0:
nums[write], nums[read] = nums[read], nums[write]
write += 1Revise
Carry the pattern into the interview
Pattern to remember
Write-pointer partition (stable)
Code pattern to memorize
write = 0
for read in 0..n-1:
if nums[read] != 0:
swap(nums[write], nums[read])
write += 1Rules to remember
- 1
writemarks the slot for the next non-zero; everything before it is already packed. - 2On a non-zero, swap it into
nums[write]and advancewrite— this preserves relative order. - 3Skip zeroes entirely;
writestays put so the next non-zero overwrites the leftmost zero. - 4Swapping (not just assigning) carries the zero to the back, so no explicit padding pass is needed.
- 5Order of the non-zeros must be stable — never reorder them.
- 6When all elements are non-zero, every swap is
nums[i]with itself — correct and harmless.
Use this pattern when
- In-place partition: keep some elements up front, push the rest to the back
- Relative order of the kept elements must be **preserved** (stable)
- `O(1)` space with a single pass and minimal writes
- Any 'move all X to one side' rearrangement
Choose another pattern when
- Order doesn't matter → two-pointer swap from both ends is fewer writes still
- Multiple categories to partition (Dutch flag) → three pointers, not one
- You must keep zero positions too → this destroys them
- Linked list, not array → splice nodes instead of swapping slots
Interview takeaway
A single write pointer that only advances on a 'keep' element is the canonical stable in-place partition. Swapping the kept element forward simultaneously slides the unwanted element back, so one pass does the whole job. The same shape powers Remove Element and the partition step of quicksort.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternCanonical stable in-place partition with a single write pointer. Recognise it whenever you must keep a subset of elements up front in order and push the rest to the back with O(1) space. Swapping fuses the pack and evict into one pass. Same shape as Remove Element, Sort Array By Parity, and quicksort's partition step.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Remove ElementSame write pointer, but keep elements != val instead of != 0.
- Remove Duplicates from Sorted ArrayWrite pointer keeps the first of each run; return the new length.
- Sort ColorsComing soonThree-way (Dutch national flag) partition — two write pointers for 0s and 2s.
- Sort Array By ParityComing soonSame partition idea splitting evens from odds with a write pointer.