Rahul Kumar

I will be honest. I have been putting off a serious DSA grind for a long time. I would watch videos, feel like I understood things, and then completely blank the moment I sat down to actually write code.
So I made a commitment: 250 days. One problem every day. No skipping. No half-measures.
And I am starting with something deliberately simple, not because it is trivial, but because the best foundations are built on problems that look easy but quietly teach you patterns you will use for the rest of your coding life.
LeetCode 283 Move Zeroes is that kind of problem.
By the end of this post, you will understand not just what the solution does, but why it works, how to think about it during an interview, and where this pattern shows up again and again in harder problems.
Let us get into it.
Given an integer arraynums, move all0s to the end of it while maintaining the relative order of the non-zero elements. You must do this in-place without making a copy of the array.
Examples:
Input: [0, 1, 0, 3, 12] Output: [1, 3, 12, 0, 0] Input: [0] Output: [0]
Constraints:
1 <= nums.length <= 10^4-2^31 <= nums[i] <= 2^31 - 1When most beginners first see this problem, their gut says:
"Let me find all the zeroes and push them to the back."
That sounds reasonable, but it immediately runs into trouble. How do you "push" a zero to the back? You would have to shift every element after it one step forward. That is an O(n) operation per zero, giving you O(n²) total. That is too slow, and it also makes the logic messy.
Here is the shift in thinking that unlocks the efficient solution:
Stop thinking about moving zeroes. Start thinking about pulling non-zero elements forward.
The zeroes do not need to be moved anywhere special. They will naturally occupy the leftover space at the end once you have collected all the non-zero elements at the front.
This is the core insight, and everything else flows from it.
This problem is a textbook application of the Fast–Slow Pointer pattern. Before we look at the code, let us understand what the two pointers actually represent.
i is the Explorer (Fast Pointer)This pointer visits every single element in the array from left to right. It does not skip anything. It is the one asking, "Is this element worth keeping up front?"
start is the Builder (Slow Pointer)This pointer tracks the next available position where a non-zero element should be placed. It only advances when a non-zero element is actually placed at its position. It is the one saying, "I am ready for the next non-zero element, send it here."
nums[i] is not zero, swap nums[i] with nums[start], then move start one step forward.nums[i] is zero, do nothing. Just let i advance.That is genuinely it. Two rules. One pass through the array.
Let us trace through the example [0, 1, 0, 3, 12] extremely carefully so there is no ambiguity.
Initial State: Array: [0, 1, 0, 3, 12] ^ start = 0 i = 0
i = 0Array: [0, 1, 0, 3, 12] ^ i=0, start=0
nums[0] = 0
The condition nums[i] !== 0 is false. This element is a zero. We do not want it at the front.
Action: Do nothing. i moves to 1. start stays at 0.
After Iteration 1: Array: [0, 1, 0, 3, 12] (unchanged) start = 0 (still waiting for first non-zero) i = 1
i = 1Array: [0, 1, 0, 3, 12] ^ i=1, start=0
nums[1] = 1
The condition nums[i] !== 0 is true. We found a non-zero element.
Action: Swap nums[1] with nums[0], the position start points to.
Before swap: [0, 1, 0, 3, 12] ^ ^ start i After swap: [1, 0, 0, 3, 12]
Then move start forward to 1.
After Iteration 2: Array: [1, 0, 0, 3, 12] start = 1 i = 2
What just happened? The 1 is now in its correct position at index 0. The 0 that was there moved to index 1. We have not lost anything. We just rearranged.
i = 2Array: [1, 0, 0, 3, 12] ^ i=2, start=1
nums[2] = 0
The condition nums[i] !== 0 is false.
Action: Do nothing. i moves to 3. start stays at 1.
After Iteration 3: Array: [1, 0, 0, 3, 12] (unchanged) start = 1 i = 3
i = 3Array: [1, 0, 0, 3, 12] ^ i=3, start=1
nums[3] = 3
The condition nums[i] !== 0 is true. Non-zero found!
Action: Swap nums[3] with nums[1], the position start points to.
Before swap: [1, 0, 0, 3, 12] ^ ^ start i After swap: [1, 3, 0, 0, 12]
Then move start forward to 2.
After Iteration 4: Array: [1, 3, 0, 0, 12] start = 2 i = 4
Notice: 3 is now in the correct position (second slot). The two zeroes are being naturally pushed toward the right side of the array, without us explicitly doing anything to them.
i = 4Array: [1, 3, 0, 0, 12] ^ i=4, start=2
nums[4] = 12
The condition nums[i] !== 0 is true. Another non-zero!
Action: Swap nums[4] with nums[2], the position start points to.
Before swap: [1, 3, 0, 0, 12] ^ ^ start i After swap: [1, 3, 12, 0, 0]
Then move start forward to 3. But i has reached the end of the array. We are done.
Final Array: [1, 3, 12, 0, 0]
Let us pause and think about why this approach is correct, not just that it produces the right output.
1. Non-zero elements always end up in their original relative order.
Every time we swap a non-zero element into start's position, we are placing it in the leftmost available slot. Since i scans left to right, elements are encountered in their original order. So the order is automatically preserved.
2. start always points to a position that is either zero or already past the array.
Everything to the left of start is a non-zero element placed in its final position. Everything at or to the right of start (up to i) is a zero or has not been visited yet.
3. Swapping instead of overwriting is key.
If we just overwrote nums[start] = nums[i] without swapping, we would lose the zero that was sitting at start. By swapping, we ensure the zero gets moved to position i instead, which is fine, because i has already been processed and will continue moving forward regardless.
4. When i and start are at the same position, we are swapping an element with itself, a harmless no-op.
This happens when the array has no zeroes to the left of a non-zero element. The algorithm handles this gracefully without any special case.
var moveZeroes = function(nums) {
let start = 0; // Slow pointer, next position for a non-zero element
for (let i = 0; i < nums.length; i++) { // Fast pointer, scans everything
if (nums[i] !== 0) {
// Swap current non-zero element into the 'start' position
let temp = nums[i];
nums[i] = nums[start];
nums[start] = temp;
// Move the slow pointer forward, next slot is ready
start++;
}
// If nums[i] is 0, do nothing. i will advance automatically
}
};Line-by-line breakdown:
let start = 0 sets the slow pointer at index 0, the first slot where a non-zero element should go.for (let i = 0; ...) is the fast pointer that visits every element.if (nums[i] !== 0) means we only act when we find something worth placing at the front.start and moves whatever was at start (a zero) to i.start++ signals that the slot has been filled and the next slot is now the target.Complexity
Explanation
Time
O(n)
We make exactly one pass through the array. Every element is visited once.
Space
O(1)
We use only two integer pointers (i and start) and a single temp variable. No extra arrays.
This is optimal. You cannot solve this problem faster than O(n) because you must at minimum inspect every element to know if it is zero or not.
Mistake 1: Using a second array.
// Wrong approach, creates a copy and violates constraints
const result = nums.filter(x => x !== 0);This is clean and readable, but it violates the in-place constraint. In an interview, this would cost you points.
Mistake 2: Moving zeroes forward instead of pulling non-zeroes.
Trying to find zeroes and shift everything after them is an O(n²) approach. Always think: "Where do I want things to go?" not "What do I want to remove?"
Mistake 3: Forgetting that two pointers do not always mean opposite ends.
Many people associate two pointers with the left/right meeting-in-the-middle pattern (used in problems like Two Sum on sorted arrays). Here, both pointers start at the left and move right at different speeds. Always ask yourself what each pointer represents.
If this question comes up in an interview, here is how to narrate your thinking:
Day 1 Completed
250 days is a long road, but every expert was once at Day 1. Tomorrow I will be tackling another problem with a fresh breakdown, dry run, and pattern analysis.
If you are following along, drop a comment with what problem you are working on. Or if something in this explanation was not clear, just ask. I will answer.
See you on Day 2.
This post is part of my 250 Days DSA Challenge, solving one problem every day with a focus on intuition, patterns, and interview-ready thinking.
Day 4 of the 250 Days DSA Challenge. LeetCode 11 Container With Most Water is a visual problem hiding a clean logical constraint. This post breaks down the full approach with a detailed dry run, pointer movement reasoning, and interview ready insights.
Rahul Kumar
LangChain's Model component is the core of every AI app. Learn how LLMs, Chat Models, and Embeddings actually work - from token generation to semantic search - with practical code and real-world examples.
Sameer Singh
Learn the 6 core building blocks of LangChain - Models, Prompts, Chains, Indexes, Memory, and Agents. Understand what each component does, why it exists, and how they work together to power real AI apps.
Sameer Singh
Master LangChain from the ground up. Learn how RAG systems work, how LLM memory is managed, and how agents take action - complete with working Python code. Your full roadmap to building real-world AI apps.
Sameer Singh
Sign in to join the discussion.