Interview Cracker GitHub

2026-09-17 — Move Zeroes

Difficulty: 🟢 Easy Pattern: Two pointers (read/write) Time-box: 20 minutes

Problem

Given an integer array, move all 0s to the end in place, keeping the relative order of the non-zero elements.

Examples

[0, 1, 0, 3, 12]  ->  [1, 3, 12, 0, 0]
[0]               ->  [0]
[1, 2, 3]         ->  [1, 2, 3]        # nothing to move
[0, 0, 1]         ->  [1, 0, 0]

Constraints


Hint (open only if stuck after 10 minutes)

Building a new list of non-zeroes and padding with zeroes is easy but uses O(n) extra space.

Instead, use two indices moving in the same direction. Let read scan every element, and let write mark where the next non-zero belongs. write only advances when you actually place something.

After read finishes, what does everything from write onwards have to be?


Solution
def move_zeroes(nums: list[int]) -> None:
    """Move all zeroes to the end, in place, preserving order."""
    write = 0

    # Pass 1: compact every non-zero toward the front.
    for read in range(len(nums)):
        if nums[read] != 0:
            nums[write] = nums[read]
            write += 1

    # Pass 2: everything from `write` on must be zero.
    for i in range(write, len(nums)):
        nums[i] = 0

How it works. write lags behind read by exactly the number of zeroes seen so far. The first pass copies the non-zeroes forward into their final positions; the second fills the tail. Because read only ever moves forward and write never overtakes it, no unprocessed value is ever overwritten.

A one-pass variant using a swap:

def move_zeroes(nums: list[int]) -> None:
    write = 0
    for read in range(len(nums)):
        if nums[read] != 0:
            nums[write], nums[read] = nums[read], nums[write]
            write += 1

This is one pass, but it performs a swap per non-zero element rather than a single write, so it is not automatically faster — for an array that is mostly zeroes it does fewer writes; for an array with few zeroes it does more.

Complexity. Time: O(n) — each element is examined once (twice in the two-pass version, still linear). Space: O(1) — two indices, no allocation.

Edge cases that matter. - Empty array — both loops simply do not run. - All zeroes — write stays 0, the second pass rewrites everything. - No zeroes — every element is copied onto itself, which is harmless. - A single element, zero or not.

Common wrong answer. Removing zeroes while iterating:

for i, n in enumerate(nums):
    if n == 0:
        nums.remove(n)     # WRONG
        nums.append(0)

Mutating a list while iterating it skips elements, so [0, 0, 1] comes out wrong. It is also O(n²), because remove shifts the whole tail each time.