Moving Zeros to the End in Python: From Slow to Super Fast
As a software engineer and algorithm specialist, I've worked extensively with data structure optimization problems.
Today, I'll walk you through a common interview question:
how to move all zeros in a list to the end while keeping non-zero numbers in the same order.
We'll look at the slow way first, then see how to turbocharge it.
Why This Problem Matters
If you're learning data structures and algorithms in Python, problems like moving zeros to the end teach you how to write faster, cleaner, and more memory-efficient code.
Whether you're just starting out or preparing for coding interviews, mastering these optimization patterns will make you a stronger Python developer.
And trust me, I've seen countless developers (and even seniors) fall into the trap of writing slow code when a better way exists.
Let's break it down so simply that even a 2-year-old coder could get it.
The Naive Approach: It Works, But It's Slow
Here's the first solution. It uses a new list to store the zeros and pops them out of the original list:
def move_zeros(nums):
temp = []
for i in range(len(nums)-1, -1, -1): # Go backward
if nums[i] == 0:
temp.append(0)
nums.pop(i)
nums.extend(temp)
return nums
What's Wrong?
pop(i) shifts all the elements after index i, making it O(n) each time. So, doing this in a loop results in O(n²) time complexity.
It also uses extra space (temp) – O(n) space.
It modifies the list during iteration, which is risky and slow.
Real-World Impact
If your list has 1 million numbers, this method could take minutes instead of milliseconds. That's bad news in performance-critical systems.
The Optimal Approach: Fast and Memory-Efficient
Let's upgrade it to an O(n) time and O(1) space solution.
from typing import List
def move_zeros(nums: List[int]) -> List[int]:
"""
Move all zeros in the list to the end while keeping the order of non-zero elements.
Args:
nums (List[int]): List of integers.
Returns:
List[int]: Modified list with zeros moved to the end.
"""
pos = 0 # This is where we place the next non-zero item
# Step 1: Move all non-zero elements to the front
for i in range(len(nums)):
if nums[i] != 0:
nums[pos] = nums[i]
pos += 1
# Step 2: Fill the rest of the list with zeros
for i in range(pos, len(nums)):
nums[i] = 0
return nums
Why This Works So Well
Metric Old Version Optimized Version
Time O(n²) O(n)
Space O(n) O(1)
Readability Moderate High
Risk High (pop+edit) Low (safe swap)
Let's Test It Like a Pro
This unit test setup checks many edge cases. The same tests work for both versions:
import unittest
class TestMoveZeros(unittest.TestCase):
def test_1(self):
self.assertEqual([1,3,12,0,0], move_zeros([0,1,0,3,12]))
def test_2(self):
self.assertEqual([0,0,0,0], move_zeros([0,0,0,0]))
def test_3(self):
self.assertEqual([1]*10**5 + [0]*10**5, move_zeros([1]*10**5 + [0]*10**5))
def test_4(self):
self.assertEqual(list(range(1,10**5+1)) + [0]*10**5, move_zeros([0]*10**5 + list(range(1,10**5+1))))
def test_5(self):
self.assertEqual(list(range(10**6)) + [0]*10**6, move_zeros(list(range(10**6)) + [0]*10**6))
if __name__=="__main__":
unittest.main()
What You Should Do Next
It isn't just about moving zeros. It's about learning how to write smarter, leaner Python code. These are the kinds of patterns that:
- Make your programs run faster.
- Impress interviewers in coding interviews.
- Help you stand out as a developer.
If you liked this breakdown, head over to PythonHaven.com for more deep dives like this — all 100% fluff-free.
Next time you write a loop, ask: Can this be done smarter?