Back to Miscellaneous Coding
Q19Question 19 of 52

Move Zeroes: Two-Pointer Compaction Keeping Order

Key Concepts
  • A slow pointer marks where the next non-zero goes; a fast pointer scans every element
  • The write-and-fill approach copies non-zeros forward in pass 1 and fills zeros in pass 2
  • The swap variant moves zeros and non-zeros in a single pass, minimizing writes when zeros are rare
  • Stability (preserving non-zero order) is the core requirement; sorting or two-ends swapping violates it
  • The algorithm is O(n) time and O(1) space, done entirely in place
  • This is the same pattern as stable partitioning, reusable for any predicate

Definition

Write every non-zero to the front with a slow pointer, then pad the rest with zeros: order preserved, no extra array.
  • Two-pointer compaction – a slow pointer (insert) and a fast pointer (scan) walk the array together.
  • Write-and-fill – pass 1 copies non-zeros forward; pass 2 fills the remaining positions with zeros.
  • Swap variant – on finding a non-zero, swap it with the insert position in one pass.
  • Stability – non-zero values keep their original relative order.
  • In-place – only two index variables are used; no extra array is allocated.

Priya has a list of this month's charges at National Bank of India where cancelled charges show as zero. She wants all real charges pushed to the front in the same order they happened, with the zeros swept to the back. She keeps one finger on the next clean slot and scans with another; every real charge gets written forward, then the leftover slots become zeros.

Understanding the Concepts

What is the Move Zeroes problem?

Given an integer array, rearrange it so all zero values move to the end while the relative order of non-zero values is preserved. The operation must be done in place without allocating a second array.

At National Bank of India, Priya's monthly statement has cancelled line items showing as zero amounts scattered through the list, and the UI wants the real charges grouped at the top in their original order.

JAVA
package com.bank.core;

import java.util.Arrays;

public class MoveZeroesDemo {
    public static void main(String[] args) {
        int[] charges = {200, 0, 450, 0, 0, 100, 0, 350};
        System.out.println("Before: " + Arrays.toString(charges));
        moveZeroes(charges);
        System.out.println("After:  " + Arrays.toString(charges));
    }

    static void moveZeroes(int[] a) {
        int insert = 0;
        for (int i = 0; i < a.length; i++) {
            if (a[i] != 0) {
                a[insert++] = a[i];
            }
        }
        while (insert < a.length) {
            a[insert++] = 0;
        }
    }
}
TEXT
Before: [200, 0, 450, 0, 0, 100, 0, 350]
After:  [200, 450, 100, 350, 0, 0, 0, 0]

How does the write-and-fill approach work step by step?

Phase 1: the slow pointer insert starts at 0 and the fast pointer i scans the array. Whenever a[i] is not zero, copy it to a[insert] and advance insert. After this phase, the first insert slots hold all non-zero values in their original order.

Phase 2: fill every index from insert to the end with 0.

JAVA
static void moveZeroesWriteFill(int[] a) {
    int insert = 0;

    // Phase 1: compact non-zeros
    for (int i = 0; i < a.length; i++) {
        if (a[i] != 0) {
            a[insert] = a[i];
            insert++;
        }
    }

    // Phase 2: fill remaining with zeros
    for (int j = insert; j < a.length; j++) {
        a[j] = 0;
    }
}

How does the one-pass swap variant work?

Instead of a fill phase, swap each non-zero with the value at the insert position. This pushes zeros rightward in a single pass.

JAVA
static void moveZeroesSwap(int[] a) {
    int insert = 0;
    for (int i = 0; i < a.length; i++) {
        if (a[i] != 0) {
            int temp = a[insert];
            a[insert] = a[i];
            a[i] = temp;
            insert++;
        }
    }
}
JAVA
package com.bank.core;

import java.util.Arrays;

public class SwapDemo {
    public static void main(String[] args) {
        int[] a = {0, 1, 0, 3, 12};
        System.out.println("Before: " + Arrays.toString(a));
        moveZeroesSwap(a);
        System.out.println("After:  " + Arrays.toString(a));
    }
}
TEXT
Before: [0, 1, 0, 3, 12]
After:  [1, 3, 12, 0, 0]

How is write-and-fill different from the swap variant?

| Property | Write-and-Fill | Swap |
|----------|---------------|------|
| Passes | Two (scan + fill) | One |
| Writes when zeros are frequent | Fewer (no swap of zeros) | More (swaps zeros around) |
| Writes when zeros are rare | Same | Same (swap is with self) |
| Readability | Easier to reason about | Slightly trickier |
| Preserves order | Yes | Yes |

JAVA
// For array [0, 0, 0, 0, 1]:
// Write-fill: 1 copy + 4 fills = 5 writes
// Swap: 4 self-swaps + 1 real swap = 5 writes (but swap does 2 writes each)
// Write-fill wins on zero-heavy arrays

Why does sorting or two-ends swapping fail?

Sorting moves zeros to the front or groups them, but scrambles the non-zero order. Two-ends swapping is fast but reverses the order of non-zeros that get moved.

JAVA
// WRONG: sorting breaks non-zero order
int[] a = {3, 0, 1, 0, 5};
Arrays.sort(a);  // [0, 0, 1, 3, 5] -- order of 3,1,5 changed to 1,3,5

// WRONG: two-ends swap reverses moved elements
// Input:  [200, 0, 300, 0, 0, 700]
// Result: [200, 700, 300, 0, 0, 0] -- 700 jumped ahead of 300

How does this work with BigDecimal for banking?

For money amounts, use BigDecimal and check zero with signum() instead of ==.

JAVA
package com.bank.core;

import java.math.BigDecimal;
import java.util.Arrays;

public class MoveZeroesBigDecimal {

    static void moveZeroes(BigDecimal[] a) {
        int insert = 0;
        for (int i = 0; i < a.length; i++) {
            if (a[i].signum() != 0) {
                a[insert++] = a[i];
            }
        }
        while (insert < a.length) {
            a[insert++] = BigDecimal.ZERO;
        }
    }

    public static void main(String[] args) {
        BigDecimal[] charges = {
            new BigDecimal("200.00"), BigDecimal.ZERO,
            new BigDecimal("450.50"), BigDecimal.ZERO,
            new BigDecimal("100.00")
        };
        System.out.println("Before: " + Arrays.toString(charges));
        moveZeroes(charges);
        System.out.println("After:  " + Arrays.toString(charges));
    }
}
TEXT
Before: [200.00, 0, 450.50, 0, 100.00]
After:  [200.00, 450.50, 100.00, 0, 0]

What are the edge cases?

Edge cases include empty arrays, arrays with no zeros, arrays that are all zeros, and single-element arrays.

JAVA
package com.bank.core;

import java.util.Arrays;

public class MoveZeroesEdges {
    public static void main(String[] args) {
        test(new int[]{});          // empty
        test(new int[]{1, 2, 3});   // no zeros
        test(new int[]{0, 0, 0});   // all zeros
        test(new int[]{0});         // single zero
        test(new int[]{5});         // single non-zero
    }

    static void test(int[] a) {
        moveZeroes(a);
        System.out.println(Arrays.toString(a));
    }

    static void moveZeroes(int[] a) {
        int w = 0;
        for (int s = 0; s < a.length; s++) if (a[s] != 0) a[w++] = a[s];
        while (w < a.length) a[w++] = 0;
    }
}
TEXT
[]
[1, 2, 3]
[0, 0, 0]
[0]
[5]

What about using Java Streams?

Streams can solve this but create a new array, which violates the in-place constraint. Mentioning it shows you know the tool but understand why it is not applicable here.

JAVA
import java.util.Arrays;
import java.util.stream.IntStream;

// Creates a new array (NOT in-place, but functionally correct)
int[] input = {0, 1, 0, 3, 12};
int[] nonZeros = Arrays.stream(input).filter(x -> x != 0).toArray();
int[] result = new int[input.length];
System.arraycopy(nonZeros, 0, result, 0, nonZeros.length);
// result: [1, 3, 12, 0, 0] -- correct but uses extra array

What should a senior engineer know?

  • Stable partition. Move Zeroes is a specific case of stable partitioning. The same two-pointer technique partitions any array around any predicate while preserving relative order.
  • Write minimization. On write-expensive storage, count the writes: write-and-fill does at most 2n writes (n non-zeros + n zeros), while swap does at most 3n (each swap is 3 assignments). Choose based on the write cost.
  • In-place constraint. The whole point of the problem is demonstrating in-place modification. Allocating a second array is trivially correct but disqualifying.
  • Batch processing. For million-row settlement arrays in banking, the in-place approach avoids doubling memory consumption.

Visual Overview

TEXT
  Input: [200, 0, 450, 0, 0, 100, 0, 350]
         insert=0

  Scan:  i=0  200!=0  a[0]=200  insert=1
         i=1  0==0    skip
         i=2  450!=0  a[1]=450  insert=2
         i=3  0==0    skip
         i=4  0==0    skip
         i=5  100!=0  a[2]=100  insert=3
         i=6  0==0    skip
         i=7  350!=0  a[3]=350  insert=4

  After scan:  [200, 450, 100, 350, ?, ?, ?, ?]
                                    ^ insert=4

  Fill zeros:  [200, 450, 100, 350, 0, 0, 0, 0]

Key Takeaway

Move Zeroes rearranges an array so every zero ends up at the back while the non-zero values keep their original order, done in place. I treat it as a same-direction two-pointer compaction: a slow pointer marks the next slot for a non-zero, and a fast pointer scans the entire array. Each time the fast pointer sees a non-zero, I copy it to the slow slot and advance the slow pointer. After the scan, the front holds all non-zeros in order, and I fill the tail with zeros.

The property interviewers test is stability: the non-zero order must not change. The copy approach preserves it naturally because I write non-zeros in the exact order I meet them. Sorting or two-ends swapping would get the zeros in place but fail the ordering requirement.

The in-place constraint is the whole point of the problem. Allocating a second array is trivially correct but disqualifying. I stay within the original array using only two index variables. For money at the National Bank of India, I use BigDecimal and check with signum() rather than ==.

The senior-level insights are that this is really a stable partition around a predicate (reusable for any condition), that the write-and-fill version minimizes writes on zero-heavy arrays while the swap version is better on zero-rare arrays, and that skipping self-writes when the pointers coincide reduces memory traffic on large batch processing arrays.

Remember one sentence: a slow pointer collects non-zeros at the front in order, the tail is filled with zeros, and the result is a stable in-place partition in O(n) time with O(1) space.

---

Share
Use ← → arrow keys to navigate