Move Zeroes problem in DSA: Brute Force, Extra Array, and the Two Pointer Trick
-
Last Updated: July 16, 2026
-
By: javahandson
-
Series
Learn Java in a easy way
Learn the Move Zeroes problem in DSA with a beginner friendly guide. We compare brute force, an extra array, and the two pointer trick, with dry runs for all three and ten interview questions at the end. As a developer I will be using Java code but pseudo-codes, explanation and dry runs are language neutral and you can understand the concept easily. So don’t skip it because language is just a syntax.
The Move Zeroes problem in DSA is a small question with a big lesson hiding inside. You have probably seen it on a practice site and thought it looked easy. The catch shows up the moment somebody asks you to do it without a second array.
Here is the task in plain words. Somebody hands you an array of numbers, and some of them are zeroes. Push every zero to the end, keep the other numbers in their original order, and change the array in place.
Those last three words carry all the weight. Drop them and this is a five-minute exercise. Keep them and it becomes a genuine test of how you think about memory.
In the last article we used Kadane’s Algorithm to sweep an array once while tracking a running best. Now we sweep again, except this time we move values rather than sums. Brute force comes first, an extra array comes second, and the two pointer version lands last.
Here is the road map for the rest of the guide.
Let us pin down the rules first. Clear rules save you from silly bugs later, and reading them aloud costs about thirty seconds.
Every version of this question comes down to the same four rules.
[0, 1, 0, 3, 12].Worth knowing about the original version of this question: the array holds up to about 10,000 numbers, and any integer value is fair game, positive or negative. Only a value of exactly zero counts as a zero, which matters more than it sounds.
Take [0, 1, 0, 3, 12]. The answer is [1, 3, 12, 0, 0].
Look at the surviving order. The 1 still comes before the 3, and the 3 still comes before the 12. Nothing jumped the queue.
That ordering rule is the entire difficulty. Without it you could swap each zero with the last element and finish in seconds. With it, every move you make has to respect the sequence that was already there.
In place means the caller keeps the same array object, and you rewrite what lives inside it. Build a fresh array and hand it back, and you solved a different problem.
Why do interviewers care so much? Because allocating a second array of the same size doubles your memory. On an array of ten items nobody notices. On an array of ten million, that doubling is the difference between a service that runs and a service that falls over.
So the real question hiding in this puzzle is simple. Can you rearrange data using only a couple of extra variables? Everything below builds towards yes.
| 💡 Interview Insight Many people miss the words in place. If you build a fresh array and return it, you solved a different problem. Say the phrase out loud during the interview so they know you caught it. |
Three small ideas carry this whole problem. All of them come back in many array questions, so learn them once and reuse them forever.
In-place editing means working inside the array you already hold. No second array of the same size ever gets allocated, so your extra memory stays constant however large the input grows.
Think of rearranging books on a shelf. You could carry them all to a second shelf in the right order, or you could slide them along the shelf they are already on. The second way needs no new shelf.
Writing over an old slot is the whole technique. That feels dangerous at first, because you seem to be destroying data. The next two ideas explain why it stays safe.
Two indexes walk the same array with different jobs. One reads every value from front to back and never stops. The other marks the slot where the next non-zero value belongs.
Picture a queue at a ticket desk where some people leave without buying. The reader is the person calling names, and the writer is the seat counter. Empty-handed leavers do not fill a seat, so the counter waits.
Here is the guarantee that makes overwriting safe. The writer starts at the same place as the reader, and it advances at most once per reading step.
So the writer either sits level with the reader or trails behind it. Never ahead. Every slot the writer touches has already been read, which means no value gets destroyed before we used it.
Trace the two cases and you can see it. On a non-zero value both indexes move one step, so the gap stays the same. On a zero only the reader moves, so the gap widens by one. Nothing ever narrows it.
That gap has a meaning too. It is exactly the number of zeroes seen so far, which tells you how many slots the tail will need at the end.
The first idea most beginners reach for feels natural. Find a zero, slide everything after it one step left, and drop a zero at the end. Repeat until no zero sits out of place.
count = 0 // zeroes already parked at the end
i = 0
while i < n - count:
if nums[i] == 0:
shift every element after i one step left
nums[n-1] = 0
count = count + 1 // one more zero parked
else:
i = i + 1 // only move on when no shift happenedThe count tracks how many zeroes already sit at the back, and the loop stops before them. Skip that guard and you would shift the same zeroes forever. Notice too that the read position stays put after a shift, because a brand new value just landed there.
import java.util.Arrays;
public class MoveZeroesBrute {
public static void moveZeroes(int[] nums) {
int n = nums.length;
int count = 0;
int i = 0;
while (i < n - count) {
if (nums[i] == 0) {
for (int j = i; j < n - 1; j++) {
nums[j] = nums[j + 1];
}
nums[n - 1] = 0;
count++;
} else {
i++;
}
}
}
public static void main(String[] args) {
int[] nums = { 0, 1, 0, 3, 12 };
moveZeroes(nums);
System.out.println(Arrays.toString(nums)); // Output: [1, 3, 12, 0, 0]
}
}Let us walk the logic slowly. Every part has one clear job.
This solution edits the array in place, so it does respect the main rule. Sadly it repeats an enormous amount of work, which the next two approaches fix.
Let us trace nums = [0, 1, 0, 3, 12] by hand. First, here is what each column means, so nothing feels cryptic.
Watch two things as you read. The read index only moves forward when no shift happened. The parked count grows by one every time we send a zero to the back.
| Step | Read index | Value there | Action | Array after | Zeroes parked |
|---|---|---|---|---|---|
| 1 | 0 | 0 | Shift left, park a zero at the end | [1, 0, 3, 12, 0] | 1 |
| 2 | 0 | 1 | Not zero, so move forward | [1, 0, 3, 12, 0] | 1 |
| 3 | 1 | 0 | Shift left, park a zero at the end | [1, 3, 12, 0, 0] | 2 |
| 4 | 1 | 3 | Not zero, so move forward | [1, 3, 12, 0, 0] | 2 |
| 5 | 2 | 12 | Not zero, so move forward | [1, 3, 12, 0, 0] | 2 |
Look at step one and step two together. After the shift, the read index stayed at 0 on purpose. A fresh value, the 1, had just slid into that slot, so we had to inspect it before moving on.
Now check why the loop stops after step five. Here the length is 5 and two zeroes are parked, so the guard allows positions below 3. Since the read index has climbed to 3, the loop ends. We never look at the last two slots again, because both hold zeroes we parked ourselves.
Count the work too. Two zeroes meant two full shifts of the tail. On a big array with many zeroes, that repeated dragging is exactly what makes this approach crawl.
Each zero can trigger a full shift of everything behind it. With many zeroes that becomes n shifts of n elements, so the time lands at O(n squared).
Space stays at O(1), since a couple of counters cover everything. The memory rule is satisfied, and only the speed lets us down.
Picture the worst input: an array that starts with thousands of zeroes in a row. Every one of them drags the entire tail one step left. For a tiny array nobody notices, and for a large one it crawls.
Here is a cleaner idea. Walk the array once and copy every non-zero value into a fresh array, packed from the left. Whatever space is left over becomes the zeroes. Finally copy the result back.
temp = new array of size n // many languages zero-fill this for you
k = 0
for each num in nums:
if num != 0:
temp[k] = num
k = k + 1
copy temp back into numsOne line deserves a note. Plenty of languages hand you a fresh integer array already filled with zeroes, which is a free head start. Should yours leave that memory undefined, write the tail zeroes yourself before copying back.
import java.util.Arrays;
public class MoveZeroesExtraArray {
public static void moveZeroes(int[] nums) {
int[] temp = new int[nums.length]; // Java fills this with zeroes
int k = 0;
for (int num : nums) {
if (num != 0) {
temp[k] = num;
k++;
}
}
for (int i = 0; i < nums.length; i++) {
nums[i] = temp[i];
}
}
public static void main(String[] args) {
int[] nums = { 0, 1, 0, 3, 12 };
moveZeroes(nums);
System.out.println(Arrays.toString(nums)); // Output: [1, 3, 12, 0, 0]
}
}That final copy-back is not optional. Skip it and you have merely built a correct answer in a place nobody can see, while the caller’s array sits unchanged.
This trace is the easiest of the three. We read the input from left to right and only care about non-zero values. Remember that the scratch array arrives already full of zeroes.
| Step | Value read | Zero? | Action | Scratch array after | Next free slot |
|---|---|---|---|---|---|
| 1 | 0 | Yes | Skip it, write nothing | [0, 0, 0, 0, 0] | 0 |
| 2 | 1 | No | Copy 1 into slot 0 | [1, 0, 0, 0, 0] | 1 |
| 3 | 0 | Yes | Skip it, write nothing | [1, 0, 0, 0, 0] | 1 |
| 4 | 3 | No | Copy 3 into slot 1 | [1, 3, 0, 0, 0] | 2 |
| 5 | 12 | No | Copy 12 into slot 2 | [1, 3, 12, 0, 0] | 3 |
Notice that we never wrote a single zero ourselves. The zeroes in the last two slots were already sitting there when the array came into existence. That free head start is what makes this version so short.
The final copy-back leaves the caller looking at [1, 3, 12, 0, 0]. Compare this trace with the brute force one. Same answer, five steps instead of five plus two full shifts, and we paid for a whole second array to get it.
Time drops to O(n), which is a real win. One pass fills the scratch array and one pass copies it back, so two passes total.
Space climbs to O(n), and avoiding exactly that was the point of the problem. An interviewer will push back here almost every time, so get in first and name the cost yourself.
| 💡 Interview Insight Mentioning this approach still earns points. It shows you can reach O(n) time quickly. Just say the space cost out loud yourself, then offer to remove it. Naming your own trade-off reads as confidence, not weakness. |
Now for the version interviewers want. The trick is to stop thinking about moving zeroes at all. Instead, think about pulling the non-zero values forward. The zeroes then sort themselves out.
Keep one index for the slot where the next non-zero value belongs. Walk the array with a second index. Every time you meet a non-zero number, write it at the insert slot and push that slot forward. When the walk ends, everything from the insert slot onward becomes a zero.
insert = 0
for each num in nums:
if num != 0:
nums[insert] = num
insert = insert + 1
for i from insert to n-1:
nums[i] = 0import java.util.Arrays;
public class MoveZeroesTwoPointer {
public static void moveZeroes(int[] nums) {
int insert = 0;
for (int num : nums) {
if (num != 0) {
nums[insert] = num;
insert++;
}
}
while (insert < nums.length) {
nums[insert] = 0;
insert++;
}
}
public static void main(String[] args) {
int[] nums = { 0, 1, 0, 3, 12 };
moveZeroes(nums);
System.out.println(Arrays.toString(nums)); // Output: [1, 3, 12, 0, 0]
}
}This version does the most thinking, so let us break it apart gently.
Here is the part that trips people up. Does writing at the insert slot destroy a value we still need? It cannot, for the reason section 3.3 laid out. The writer never runs ahead of the reader, so every slot it touches was already read.
| 💡 Interview Insight A common follow-up asks why we do not swap instead of overwrite. Swapping also works and keeps the same Big-O. Overwriting is simpler to explain, though swapping does fewer total writes when zeroes are rare. Either answer is fine if you can defend it. |
Each slot gets read once and written at most once, so the time cost is O(n). Two passes happen, yet both are straight-line walks with no nesting.
Space never grows. A single index variable covers any input length, which puts us at O(1) and satisfies the in-place rule.
So this approach matches the extra-array version on speed and matches the brute force on memory. Getting the best of both is exactly why interviewers wait for it.
We traced the other two approaches already, so let us give this one the same treatment. Follow the same input, nums = [0, 1, 0, 3, 12]. Watch how the insert slot crawls forward while the reader races ahead.
| Step | Value read | Zero? | Action | Array after | Insert slot |
|---|---|---|---|---|---|
| 1 | 0 | Yes | Skip it, write nothing | [0, 1, 0, 3, 12] | 0 |
| 2 | 1 | No | Write 1 at slot 0 | [1, 1, 0, 3, 12] | 1 |
| 3 | 0 | Yes | Skip it, write nothing | [1, 1, 0, 3, 12] | 1 |
| 4 | 3 | No | Write 3 at slot 1 | [1, 3, 0, 3, 12] | 2 |
| 5 | 12 | No | Write 12 at slot 2 | [1, 3, 12, 3, 12] | 3 |
Look at step five closely. The array now reads [1, 3, 12, 3, 12], which looks wrong at a glance. Do not panic. The last two values are leftover junk, and the reader has already passed them. Since the insert slot is 3, the second loop overwrites both with zeroes.
| Fill step | Slot | Action | Array after |
|---|---|---|---|
| 1 | 3 | Write a zero | [1, 3, 12, 0, 12] |
| 2 | 4 | Write a zero | [1, 3, 12, 0, 0] |
The final array is [1, 3, 12, 0, 0]. Every zero sits at the back, and 1, 3, and 12 kept their original order. We touched each slot at most twice, and we never built a second array.

The sketch above shows the same trace. Phase one pulls the non-zero values forward, and the shaded cells show the part of the array that is already settled. Phase two fills the tail with zeroes. Many people find this picture easier than the table, so use whichever clicks for you.
Two inputs sit at the extremes, and both fall out of the same code with no special handling.
| Input | Insert slot after pass one | What pass two does | Result |
|---|---|---|---|
| [0, 0, 0] | 0 | Writes a zero over all three slots | [0, 0, 0] |
| [4, 5, 6] | 3 | Nothing, the tail is empty | [4, 5, 6] |
An all-zero array never advances the insert slot, so pass two rewrites every position with the zero it already held. Wasteful, harmless, and correct.
An array with no zeroes writes each value back onto itself, and pass two has nothing left to do. Again wasteful, again harmless. Section 9.1 shows how to skip that waste entirely.
All three approaches give the right answer. They simply charge different prices for it.
| Point | Brute Force | Extra Array | Two Pointer |
|---|---|---|---|
| Time | O(n squared) | O(n) | O(n) |
| Extra space | O(1) | O(n) | O(1) |
| Edits in place | Yes | No, needs a copy-back | Yes |
| Passes over the array | Many | Two | Two |
| Keeps the original order | Yes | Yes | Yes |
| Work at 10,000 values | Up to 100 million steps | 20,000 steps | 20,000 steps |
| Good for interviews | As a starting point | As the middle step | As the final answer |
Read the space row against the time row. Brute force is cheap on memory and expensive on time. The extra array flips that exactly. Only the two pointer version refuses to pay either price.
In real code the answer is never in doubt, and here is the short guide.
In an interview, walk that exact path. Start with brute force to prove you understand the problem, bring up the extra array to show you can hit O(n) time, then remove the extra array with two pointers. That climb from slow to clean is what interviewers grade you on.
Solve the base problem quickly and a follow-up usually arrives. These two show up most often, and both reuse the writer-and-reader idea you already have.
The original problem statement adds a bonus question: can you cut the total number of operations? Our version always performs one write per slot, even when the array holds no zeroes at all.
Swapping fixes that. Instead of overwriting, exchange the current value with whatever sits at the insert slot. Add a guard so a value never swaps with itself, and untouched arrays cost nothing.
import java.util.Arrays;
public class MoveZeroesSwap {
public static void moveZeroes(int[] nums) {
int insert = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
if (i != insert) { // skip pointless self-swaps
int temp = nums[insert];
nums[insert] = nums[i];
nums[i] = temp;
}
insert++;
}
}
}
public static void main(String[] args) {
int[] nums = { 0, 1, 0, 3, 12 };
moveZeroes(nums);
System.out.println(Arrays.toString(nums)); // Output: [1, 3, 12, 0, 0]
}
}Three things changed, and each one matters.
Cost stays at O(n) time and O(1) space, so the Big-O never changes. What changes is the constant. An array with no zeroes now performs zero writes instead of n, and that is exactly what the bonus question was asking for.
A neat variation flips the target. Send every zero to the front, and keep the non-zero values in their original order behind them. So [0, 1, 0, 3, 12] should become [0, 0, 1, 3, 12].
Resist the urge to invent a new technique. Just run the same algorithm backwards: start both indexes at the last slot and walk towards the front.
import java.util.Arrays;
public class ZeroesToFront {
public static void zeroesToFront(int[] nums) {
int insert = nums.length - 1;
for (int i = nums.length - 1; i >= 0; i--) {
if (nums[i] != 0) {
nums[insert] = nums[i];
insert--;
}
}
while (insert >= 0) {
nums[insert] = 0;
insert--;
}
}
public static void main(String[] args) {
int[] nums = { 0, 1, 0, 3, 12 };
zeroesToFront(nums);
System.out.println(Arrays.toString(nums)); // Output: [0, 0, 1, 3, 12]
}
}Everything mirrors the forward version. The writer still trails the reader, except both now travel right to left, so the same safety guarantee holds.
Walking backwards to preserve order is a pattern worth remembering. Merge Sorted Array leans on the same trick, and once you have seen it twice it stops feeling clever and starts feeling obvious.
A handful of small traps catch beginners on this problem. Read them once and you will dodge most of them.
[1, 3, 12, 3, 12].Empty and single-element arrays need no special handling either. Every approach above walks them harmlessly and leaves them exactly as they were.
Let us tie everything into one small program. Sensor logs often use 0 to mean “no reading”. This program compacts the real readings to the front and reports what it did.
import java.util.Arrays;
public class ReadingCompactor {
public static void report(int[] readings) {
if (readings == null || readings.length == 0) {
System.out.println("No readings to process.");
return;
}
int[] before = Arrays.copyOf(readings, readings.length);
int insert = 0;
for (int value : readings) {
if (value != 0) {
readings[insert] = value;
insert++;
}
}
int kept = insert;
while (insert < readings.length) {
readings[insert] = 0;
insert++;
}
System.out.println(Arrays.toString(before) + " -> " + Arrays.toString(readings)
+ " (" + kept + " kept, " + (readings.length - kept) + " blanked)");
}
public static void main(String[] args) {
report(new int[] { 0, 1, 0, 3, 12 });
report(new int[] { 0, 0, 0 });
report(new int[] { 4, 5, 6 });
}
}[0, 1, 0, 3, 12] -> [1, 3, 12, 0, 0] (3 kept, 2 blanked) [0, 0, 0] -> [0, 0, 0] (0 kept, 3 blanked) [4, 5, 6] -> [4, 5, 6] (3 kept, 0 blanked)
Three inputs, three different shapes of answer. That is the point of the exercise.
Notice the small but useful trick in the middle. The insert slot after pass one is exactly the number of real readings, so we grab it before the fill loop moves it. One variable answers two questions at once.
Notice how little changed from the plain two pointer version too. The scan is identical. We only added a guard at the top, a snapshot for the report, and a friendlier line at the bottom.
A: Somebody hands you an array of integers, and you must push every zero to the end while keeping the non-zero numbers in their original relative order. The array changes in place, so the method returns nothing.
A: The two pointer approach wins. Keep an insert slot for the next non-zero value, walk the array once writing every non-zero value there, then fill the rest with zeroes. That gives O(n) time and O(1) extra space.
A: It means rewriting the array the caller already holds, using only a couple of extra variables. Allocating a second array of the same size doubles your memory, which is exactly what the problem asks you to avoid.
A: Sorting reorders the non-zero numbers too, which breaks the main rule. The problem requires that values like 1, 3, and 12 keep their original relative order.
A: No. The insert slot never runs ahead of the reading position, so any slot you overwrite was already read. Leftover junk in the tail gets replaced with zeroes in the second loop.
A: Brute force shifting runs in O(n squared) time with O(1) space. The extra array version runs in O(n) time but uses O(n) space. Two pointers give O(n) time with O(1) space, which beats both.
A: Both work and share the same Big-O. Overwriting is easier to explain, while swapping performs fewer writes when zeroes are rare. Either is fine in an interview if you can defend the choice.
A: Use the swap variant and guard against swapping a slot with itself. An array holding no zeroes then performs no writes at all, instead of rewriting every position.
A: Run the same algorithm backwards. Start both indexes at the last slot, walk towards the front, then fill the remaining front slots with zeroes. Order is preserved for the same reason as before.
A: Try an array of all zeroes, an array with no zeroes, a single element, zeroes only at the front, and zeroes only at the back. Those five inputs catch nearly every bug here.
Let us wrap up what we covered. The Move Zeroes problem in DSA looks like a beginner warm-up, and in a way it is. Yet the two pointer idea inside it is a real skill you will reuse constantly.
We started with brute force, dragging the tail left every time a zero appeared. Correct and in place, but slow at O(n squared), because each zero can shift the entire array.
The extra array came next and pulled the time down to O(n). Copy the non-zero values into a scratch array, let the leftover slots serve as zeroes, then copy back. Fast and simple, at the cost of doubling your memory.
Two pointers finished the job by refusing both prices. Pull the non-zero values forward with a writer that trails the reader, then flood the tail with zeroes. That is O(n) time and O(1) space at the same time.
Along the way we handled the usual follow-ups. Swapping instead of overwriting cuts the write count to nothing on a clean array. Running the same walk backwards sends the zeroes to the front instead.
So take the pattern, not just the answer. Keep a writer that only advances when it stores something, let the reader run ahead, and clean up whatever the writer left behind. Once that habit feels natural, Remove Duplicates, Remove Element, and a shelf of string problems all get easier.