Mastering LeetCode Problem 0283

Problem 0283: Move Zeroes

Array manipulation problems on LeetCode often look deceptively simple — until you add constraints like:

  • “Do it in-place
  • “Preserve the relative order of elements”
  • “Use O(1) extra space

LeetCode 283 – Move Zeroes is a perfect example. The task sounds straightforward:

Move all zeroes to the end of the array while keeping the relative order of the non-zero elements.

But designing a clean, efficient solution that works in-place and passes all edge cases is exactly what interviewers care about.

In this post, we’ll walk through:

  • The problem statement in simple terms
  • Why a naïve solution is not ideal
  • The core idea: two-pointer in-place compaction
  • A clean Python implementation
  • Step-by-step walkthrough
  • Time & space complexity
  • Common interview mistakes and how to avoid them
  • Key takeaways for real interviews

1. Problem Overview

You are given an array of integers nums. Your goal:

Move all 0s to the end of the array, without changing the relative order of the non-zero elements.

1.1. Constraints:

  • You must modify the array in-place.
  • You should aim for O(n) time and O(1) extra space.
  • You must not return a new array — just update the input.

1.2. Example 1

Input:  nums = [0, 1, 0, 3, 12]
Output: [1, 3, 12, 0, 0]

The non-zero elements [1, 3, 12] keep their relative order, and all zeroes are moved to the end.

1.3. Example 2

Input:  nums = [0]
Output: [0]

Nothing to move; the array stays the same.


2. First Ideas: Why the Naïve Approach Isn’t Great

If there were no constraints about in-place modification or extra space, a very simple strategy might be:

  1. Build a new array with all non-zero elements.
  2. Count how many zeroes there were.
  3. Append that many zeroes to the end.

Something like:

non_zeros = [x for x in nums if x != 0]
zeros_count = len(nums) - len(non_zeros)
result = non_zeros + [0] * zeros_count

This works logically, but:

  • It allocates extra memory proportional to n (the array length).
  • It does not modify the input array in-place.
  • In a real interview and on LeetCode, this usually fails the constraints.

We need something better: in-place, O(1) extra space, and still O(n) time.


3. Key Insight: In-Place Compaction with Two Pointers

To satisfy all constraints, we want to:

  • Pull all non-zero elements to the front of the array.
  • Maintain their relative order.
  • Let all zeroes naturally “fall” to the back.

This is a classic application of the two-pointer pattern:

  • One pointer scans the array.
  • One pointer tracks where to write the next valid element.

3.1. Pointer Roles

Let’s define:

  • j – the scan pointer that iterates over every index from 0 to n - 1.
  • i – the write pointer, indicating the position where the next non-zero element should go.

The algorithm:

  1. Initialize i = 0.
  2. For each j from 0 to len(nums) - 1:
    • If nums[j] is non-zero:
      • Swap nums[i] and nums[j].
      • Increment i (because we placed a non-zero element at position i).
  3. When done, all non-zero elements are compacted at the beginning, and all zeroes are at the end.

3.2. Why This Preserves Order

Notice:

  • We scan from left to right.
  • The first non-zero we see goes to position 0.
  • The second non-zero goes to position 1.
  • And so on…

We never reorder non-zero elements relative to each other; we just move them forward, keeping their original sequence.


4. Python Implementation (In-Place Two-Pointer Solution)

Here’s a clean and interview-ready implementation in Python, based on the algorithm described above:

from typing import List

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Moves all zeros in the list to the end while preserving
        the relative order of non-zero elements.

        This function modifies nums in-place and returns None.
        """

        i = 0  # index to place the next non-zero element

        # j scans through the entire array
        for j in range(len(nums)):
            # When we find a non-zero, we want it at index i
            if nums[j] != 0:
                nums[i], nums[j] = nums[j], nums[i]
                i += 1

This matches the logic of your solution, with the correct method signature for LeetCode (self included) and no unnecessary changes to the algorithm.


5. Step-by-Step Walkthrough

Let’s take the example:

nums = [0, 1, 0, 3, 12]

Initial:

  • i = 0
  • j will go from 0 to 4

5.1. Iteration 1: j = 0

  • nums[j] = nums[0] = 0
  • It is zero → do nothing
  • i stays 0
  • Array remains: [0, 1, 0, 3, 12]

5.2. Iteration 2: j = 1

  • nums[1] = 1 (non-zero)
  • Swap nums[i] and nums[j] → swap nums[0] and nums[1]
nums = [1, 0, 0, 3, 12]
i = 1

We’ve placed 1 at the front.

5.3. Iteration 3: j = 2

  • nums[2] = 0
  • It is zero → do nothing
  • i remains 1
  • Array: [1, 0, 0, 3, 12]

5.4. Iteration 4: j = 3

  • nums[3] = 3 (non-zero)
  • Swap nums[i] and nums[j] → swap nums[1] and nums[3]
nums = [1, 3, 0, 0, 12]
i = 2

Now 1 and 3 (first two non-zeros) are at positions 0 and 1.

5.5. Iteration 5: j = 4

  • nums[4] = 12 (non-zero)
  • Swap nums[i] and nums[j] → swap nums[2] and nums[4]
nums = [1, 3, 12, 0, 0]
i = 3

Loop ends.

Final array:

[1, 3, 12, 0, 0]

All non-zero elements are at the beginning, preserving their original order [1, 3, 12].
All zeroes are at the end.


6. Edge Cases

A robust solution should handle edge cases gracefully. Let’s look at a few.

6.1. All Zeroes

nums = [0, 0, 0]
  • The if nums[j] != 0 condition is never true.
  • i stays 0.
  • The array stays [0, 0, 0] — which is already correct.

6.2. No Zeroes

nums = [1, 2, 3]
  • Every element is non-zero.
  • For each j, we swap nums[i] with nums[j], but since i == j at each step:
    • We are effectively swapping an element with itself.
  • The array stays [1, 2, 3].

This shows the algorithm doesn’t break or do unnecessary complex work when the array is already “ideal”.

6.3. Zeroes Already at the End

nums = [1, 2, 3, 0, 0]
  • The non-zero part is already at the front.
  • The algorithm will perform only self-swaps for j = 0, 1, 2, and ignore the zeros at 3 and 4.
  • The final array remains [1, 2, 3, 0, 0].

6.4. Single Element

nums = [0]   -> [0]
nums = [5]   -> [5]

Both are trivial but important to handle correctly. The loop runs once, and the logic still holds.


7. Time and Space Complexity

7.1. Time Complexity

We have:

  • A single loop where j goes from 0 to n - 1, where n = len(nums).
  • Each element is processed exactly once.
  • Swaps are constant time.

So the overall time complexity is:

$\text{Time Complexity} = O(n)$

7.2. Space Complexity

We use only:

  • Two integer variables (i and j) in the process.

No additional arrays or data structures are created, so the extra space used is constant:

$\text{Space Complexity} = O(1)$

This exactly matches the in-place, O(1) space requirement in the problem.


8. Common Interview Mistakes

This problem is popular in interviews because it reveals how well you handle constraints and in-place logic. Here are some common pitfalls:

8.1. Creating a New Array

A typical incorrect approach:

non_zeros = [x for x in nums if x != 0]
zeros = [0] * (len(nums) - len(non_zeros))
nums = non_zeros + zeros

This may “work” in a vacuum, but:

  • It does not modify the original nums in-place.
  • It uses extra space proportional to n.
  • On LeetCode and in interviews, this often fails the required constraints.

8.2. Using remove and append Repeatedly

Another inefficient pattern:

for x in nums:
    if x == 0:
        nums.remove(0)
        nums.append(0)

Issues:

  • list.remove() is O(n) because it has to shift elements.
  • Combined with the loop, this often devolves into O(n²) time.
  • It’s also error-prone due to changing the list while iterating over it.

8.3. Destroying the Relative Order

Some candidates try to:

  • Count zeros
  • Filter non-zeros
  • Then write non-zeros in arbitrary order

If you don’t carefully maintain the original sequence of non-zero elements, the solution becomes incorrect, even if zeros are at the end.

8.4. Overcomplicating the Logic

Sometimes, solutions end up with:

  • Multiple passes
  • Many special cases
  • Unnecessary conditions

The beauty of the two-pointer approach is that it’s both simple and optimal.


9. Interview Takeaways

LeetCode 283 – Move Zeroes is more than just an array cleanup problem. It’s a compact test of:

  • Whether you think about in-place algorithms naturally
  • How comfortable you are with the two-pointer pattern
  • Whether you pay attention to constraints like preserving order and O(1) extra space

Patterns you reinforce here are directly applicable to other problems:

  • Remove Element (LeetCode 27)
  • Remove Duplicates from Sorted Array (LeetCode 26)
  • General “filter in-place” or “compact array” tasks
  • Partitioning arrays based on a condition

Once you internalize the idea:

“Use one pointer to scan, and another to write the next valid element.”

you’ll find that a whole family of array problems becomes much easier.