Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Remove every occurrence of `val` in-place and return `k`, the count of surviving elements; the first `k` slots of `nums` must hold those survivors (order doesn't matter). The canonical answer is a slow/fast two-pointer pass: a write pointer `k` only advances when `fast` lands on a keeper, so `nums[k++] = nums[fast]` compacts the array in one pass, O(n) time and O(1) space.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you can mutate an array in-place with O(1) space using two pointers, and whether you correctly return a count while treating the tail as throwaway.
Where candidates slip
Trying to physically delete/shift elements (O(n²)), or returning the modified array instead of the integer k.
How to stand out
Name the write-pointer pattern, note that survivor order is unconstrained, and offer the swap-with-last variant as a write-minimizing optimization when removals are rare.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewRemove all occurrences of val, in-placeDoes survivor order matter?YesNoExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2 · Optimal
Slow/Fast Write Pointer (Optimal)
1Hint 1Explore hintHide hint
You're allowed to overwrite the array and ignore everything past some index k. Could you build the answer at the front of the same array as you scan it?
2Hint 2Explore hintHide hint
Keep two indices: one that reads every element (fast), and one that marks where the next keeper should be written (k). When does k move?
3Hint 3Explore hintHide hint
k only advances when fast points at an element that is NOT val. On a keeper, do nums[k] = nums[fast] then k++. At the end, return k.
A · Core idea
The trick is to realize the problem hands you a free scratchpad: the array itself. Because slots from index k onward are declared 'not important', you can build the compacted result in-place at the front while you read the original values left to right.
The two pointers play different roles. The fast pointer is a pure reader — it visits every index 0…n−1 exactly once, no exceptions. The k pointer is a writer that marks the boundary between 'already-committed keepers' and 'free space'. The invariant that makes the whole thing correct: at every moment, nums[0..k) contains exactly the non-val elements seen so far, in their original relative order. Each step either preserves that invariant (when fast hits a val, we skip and k stays put) or extends it by one (when fast hits a keeper, we copy it to nums[k] and bump k).
Why overwriting never corrupts data you still need. Since k only ever lags behind or equals fast (k ≤ fast always), the slot we write to, nums[k], has already been read — or it is nums[fast] itself, a harmless self-copy. We can never clobber a value we haven't yet processed. That monotone k ≤ fast relationship is the safety guarantee, and it's worth stating out loud in an interview.
Why this beats the naive 'delete and shift'. The instinct from working with lists is to find each val and shift everything after it left by one — but each shift is O(n) and there can be O(n) of them, giving O(n²). The write pointer sidesteps shifting entirely: instead of removing bad elements, we collect good ones into the front. Same result, one pass, no shifting.
Why we return `k` and not the array. The grader reconstructs the multiset from nums[0..k), so k is the only contract that matters. The leftover tail is genuinely irrelevant, which is why we never bother to clear it.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
nums = [0, 1, 2, 2, 3, 0, 4, 2], val = 2Array state
fast=2
nums[2]=2 == val → skip, k stays 2
k = 5, nums[0..5) = [0, 1, 3, 0, 4]C · Code with teaching comments
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
Reframe 'remove the bad ones' as 'collect the good ones to the front'. That inversion is the whole pattern — once you stop thinking about deletion and start thinking about a write cursor, shifting disappears and the problem collapses to one linear pass.
ComplexityO(n) time, O(1) space — A single left-to-right pass touches each element once → O(n). Two integer indices and no auxiliary structure → O(1) extra space.
E · Edge cases and pitfalls6nums = [], val = 0 — Loop body never executes; return k = 0.Explore edge casesHide edge cases
Edge cases handled
nums = [], val = 0Loop body never executes; return k = 0.nums = [2, 2, 2], val = 2Every element equals val → k never advances → return 0.nums = [4, 5], val = 3No element equals val → every step writes nums[k]=nums[fast] (self-copy) → return 2.
Pitfalls
- Advancing k on every iteration instead of only on keepers — corrupts the count.
- Returning the array instead of the integer k.
- Trying to clear or shift the tail — unnecessary work that risks O(n²).
Approach 2 of 2
Swap With Last Element (Write-Minimizing)
1Hint 1Explore hintHide hint
Since order doesn't matter, when you hit a val you don't have to shift — what if you just drop a fresh element on top of it from the unprocessed end?
2Hint 2Explore hintHide hint
Keep a shrinking length n. When nums[i] == val, copy nums[n-1] into nums[i] and decrement n — but don't advance i yet, because the moved element is unexamined.
3Hint 3Explore hintHide hint
If nums[i] != val, advance i normally. Stop when i reaches n. The answer is the final n.
A · Core idea
This variant exploits the 'order doesn't matter' permission more aggressively. Instead of streaming keepers forward, it treats the array as a pool: when index i lands on a val, we don't shift anything — we just grab the element from the current last live slot, drop it into position i, and shrink the live region by one.
Why we don't advance `i` after a swap. The element we pulled from the end (nums[n−1]) has never been examined — it could itself be a val. So after the swap we must re-test the same index i on the next iteration. Advancing i here is the classic bug: it lets a val that was swapped in slip through uninspected.
Why this can be cheaper than the slow/fast pass. In the standard write-pointer approach, every keeper is copied to nums[k] even when k == i — usually those are harmless self-copies, but they're still writes in some implementations. Here, a keeper costs zero writes: we only write when we actually remove something. If the array has very few vals (say one removal in a thousand), this does ~1 write instead of ~1000. When removals dominate, the two are comparable, so this is a situational optimization, not a strict win.
The shrinking boundary `n` is the survivor count. Each removal decrements n; each keeper just nudges i rightward. The loop ends when i and n meet, and n is exactly the number of survivors — the same k the other method returns.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
nums = [0, 1, 2, 2, 3, 0, 4, 2], val = 2i=0, n=8
nums[0]=0 != 2 → i→1
n=8k = 5, nums[0..5) = [0, 1, 4, 0, 3] (some order)C · Code with teaching comments
Reference implementation previewpublic int removeElement(int[] nums, int val) { int n = nums.length; int i = 0; while (i < n) { if (nums[i] == val) {Explore the full codeHide the full code
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
When order is free and deletions are rare, fill holes from the back instead of streaming from the front. You convert an O(removals) shifting cost into O(1) per removal, and keepers cost nothing.
ComplexityO(n) time, O(1) space — Each element is examined at most a constant number of times before i passes it or n shrinks past it → O(n) time, O(1) space. Writes happen only on removals, not on keepers.
E · Edge cases and pitfalls5nums = [1], val = 1 — i=0,n=1: nums[0]=nums[0] self-copy, n→0, loop ends → return 0.Explore edge casesHide edge cases
Edge cases handled
nums = [1], val = 1i=0,n=1: nums[0]=nums[0] self-copy, n→0, loop ends → return 0.nums = [2, 3], val = 2i=0: swap nums[0]=nums[1]=3, n→1; i=0: 3!=2 → i→1==n stop → return 1.
Pitfalls
- Advancing i right after a swap — the moved-in element goes unchecked and a val can survive.
- Using length() in the loop bound instead of the shrinking n.
- Assuming output order matches input — it generally won't; only the multiset of survivors is guaranteed.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic int removeElement(int[] nums, int val) { int k = 0; for (int fast = 0; fast < nums.length; fast++) { if (nums[fast] != val) { nums[k] = nums[fast];Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Shifting instead of compacting
Erasing each val and shifting the rest left is O(n) per removal and O(n²) overall — and easy to get off-by-one. Collect keepers with a write pointer instead.
Explore the code comparisonHide the code comparison
Shifting instead of compacting
Erasing each val and shifting the rest left is O(n) per removal and O(n²) overall — and easy to get off-by-one. Collect keepers with a write pointer instead.
Don't write this
for (int i = 0; i < nums.length; i++) {
if (nums[i] == val) {
for (int j = i; j < nums.length - 1; j++) nums[j] = nums[j + 1];
}
}Write this instead
int k = 0;
for (int x : nums) {
if (x != val) nums[k++] = x;
}
return k;2Advancing the write pointer unconditionally
If k++ runs even when nums[fast] == val, the count is wrong and val elements leak into nums[0..k).
Explore the code comparisonHide the code comparison
Advancing the write pointer unconditionally
If k++ runs even when nums[fast] == val, the count is wrong and val elements leak into nums[0..k).
Don't write this
k = 0
for fast in range(len(nums)):
if nums[fast] != val:
nums[k] = nums[fast]
k += 1
return kWrite this instead
k = 0
for fast in range(len(nums)):
if nums[fast] != val:
nums[k] = nums[fast]
k += 1
return k3Advancing i after a back-swap
In the swap-with-last variant, the element pulled from the end is unexamined; advancing i skips it and a val can survive in nums[0..k).
Explore the code comparisonHide the code comparison
Advancing i after a back-swap
In the swap-with-last variant, the element pulled from the end is unexamined; advancing i skips it and a val can survive in nums[0..k).
Don't write this
if (nums[i] == val) {
nums[i] = nums[n - 1];
n--;
i++;
}Write this instead
if (nums[i] == val) {
nums[i] = nums[n - 1];
n--;
} else {
i++;
}Revise
Carry the pattern into the interview
Pattern to remember
Slow/fast write pointer (in-place compaction)
Code pattern to memorize
k = 0
for x in nums:
if x != val:
nums[k] = x
k += 1
return kRules to remember
- 1Return the count
k, not the array — the grader reads onlynums[0..k). - 2Order of survivors is free — anything past index
kis ignored, so you may leave garbage there. - 3Advance the write pointer
konly on a keeper; the read pointer advances every step regardless. - 4Writing
nums[k] = nums[fast]whenk == fastis a harmless self-copy — no special-case needed. - 5If deletions are rare, the swap-with-last variant minimizes writes — mention it as the optimization.
Use this pattern when
- Delete-in-place / compact an array to the front with O(1) extra space
- Return a length and ignore the tail rather than physically shrinking
- Filtering a sequence where output order is irrelevant
- Any 'remove all X' where you can overwrite rather than shift
Choose another pattern when
- Order of the tail matters or you must shift elements → this still works but think Remove Duplicates
- You must return a brand-new array → no in-place constraint, just filter
- Removing by index range, not by value → that's a splice problem
- The array is sorted and you want dedupe → Remove Duplicates from Sorted Array
Interview takeaway
The slow/fast write pointer is the foundational in-place array trick: one pointer reads everything, the other only commits keepers. Internalize it here and you'll instantly solve Remove Duplicates, Move Zeroes, and Sort Colors — they're all the same skeleton.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternCanonical Slow/Fast Write Pointer for in-place array compaction. Recognise it whenever you must filter or partition an array with O(1) extra space and the tail is throwaway. Same skeleton as Remove Duplicates, Move Zeroes, and Sort Colors.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Remove Duplicates from Sorted ArraySame write pointer, but the keep-condition compares to the previous kept element instead of a fixed val.
- Remove Duplicates from Sorted Array IIWrite pointer that allows at most two of each value: compare nums[fast] to nums[k-2].
- Move ZeroesRemove Element with val = 0, then fill the tail with zeros to keep order.
- Sort ColorsComing soonThree-way partition (Dutch national flag) — a multi-pointer cousin of this compaction.