Remove Element
The array is not shortened. The judge inspects only a prefix, so the job is to compact the values you keep into that prefix and return its length.

Remove Element
Given an integer array nums and an integer val, remove all occurrences of val in place and return k, the number of retained elements. The first k positions must contain exactly the elements not equal to val; their order may be arbitrary, and the remaining positions are irrelevant.
Constraints
- The array length is between 0 and 100 inclusive.
- Each array value is between 0 and 50 inclusive.
- val is between 0 and 100 inclusive.
Important details
- The operation must be performed in place.
- The retained elements do not need to preserve their original order.
- Only the first k elements and the returned count are judged; values beyond that prefix are irrelevant.
Key topics
The array is not shortened. The judge inspects only a prefix, so the job is to compact the values you keep into that prefix and return its length.
Read the Judged-Prefix Contract
You receive an integer array nums and a value val. Remove every occurrence of val in place and return k, the number of values that are not equal to val.
The contract has three obligations:
- Return the count of retained values.
- Put those values in
nums[0:k]. - Treat every position from
nums[k]onward as irrelevant.
“In place” means modifying the existing array. It does not mean physically resizing it. That distinction is the key to this problem: the storage keeps its original length, while the first k positions become the logical result.
For example:
nums = [3, 2, 2, 3], val = 3
There are two retained values, so this is valid:
k = 2
nums = [2, 2, _, _]
The underscores can contain anything. The judge cares about the prefix nums[0:2], not the suffix.
That is why this is a useful Remove Element solution: it solves a judged-prefix contract rather than pretending that an array must be physically shortened.
Recognize In-Place Compaction
Each input value can be classified independently:
- If
nums[read] == val, skip it. - Otherwise, retain it and place it at the next open position in the prefix.
This is compaction. One pointer reads the input; another writes the filtered result.
readvisits every original position.writemarks the next position where a retained value belongs.
The algorithm is:
write = 0
for read from the first position through the last:
if nums[read] != val:
nums[write] = nums[read]
write += 1
return write
When the current value is removed, only read advances. When the current value is retained, copy it to write, then advance write.
The important safety fact is write <= read. The write pointer never moves ahead of the read pointer, so a write cannot destroy an unread value. At worst, a retained value is written back to its current position.
This is a two-pointer overwrite pattern. The pointers have different jobs—inspect and compact. It is not a sliding window, because no contiguous range is being maintained, and it is not fast-and-slow pointer logic, because the pointers do not move at unequal rates to detect cycles or spacing.
A new filtered list would be simpler to write:
filtered = [x for x in nums if x != val]
But that creates separate storage. Rebinding nums to filtered would also leave the caller’s original list unchanged. The write pointer gives us the same valid prefix while reusing the original list.
Prove the Write Invariant
The loop is easiest to reconstruct from this invariant:
After processing every position before
read,nums[0:write]contains exactly the processed values that are not equal toval, andwriteequals their count.
This names the obligation behind both state variables.
Initialization
At the beginning, read = 0 and write = 0. No values have been processed, and the valid prefix is empty. The invariant holds.
When the value is removed
If nums[read] == val, do not write it. Advance read to inspect the next input. The valid prefix remains unchanged, so the invariant still holds.
When the value is retained
If nums[read] != val, write it at nums[write]. This fills the next open position in the valid prefix. Then increment write, so the pointer again equals the number of retained values processed so far.
Termination
When read reaches the end, every input position has been classified. By the invariant, nums[0:write] contains exactly the retained values. Returning write therefore returns the required count.
For nums = [3, 2, 2, 3] and val = 3, the state looks like this:
read | write | Action | Valid prefix |
|---|---|---|---|
| 0 | 0 | Skip 3 | [] |
| 1 | 0 | Write 2 at index 0 | [2] |
| 2 | 1 | Write 2 at index 1 | [2, 2] |
| 3 | 2 | Skip 3 | [2, 2] |
The final value of write is 2. The suffix may contain old values or additional copies; that is not a correctness failure because it is outside the judged prefix.
Invariant to remember:
writeis the length of the valid prefix and the next destination for a retained value. It is not the index of the last retained value.
That distinction prevents the classic off-by-one error. Return write, not write - 1.
Stable Compaction Versus Swap With the End
The stable method preserves the relative order of retained values. For example, [4, 2, 6, 2, 8] becomes [4, 6, 8, ...]. I prefer this version in interviews because its invariant is short, its control flow is predictable, and it is easy to defend.
The problem does not require retained order, however. That permits a second method: replace an unwanted value with the last value in the active region, then shrink that region.
def remove_element_unordered(nums: list[int], val: int) -> int:
i = 0
end = len(nums)
while i < end:
if nums[i] == val:
nums[i] = nums[end - 1]
end -= 1
else:
i += 1
return end
Here, [0:end] is the active region. When nums[i] equals val, the replacement from nums[end - 1] has not been classified yet. Therefore, i must not advance. The next iteration checks the replacement again.
For [2, 2, 7] with val = 2, the first replacement produces [7, 2, 2]. The value 7 at index 0 still needs checking. If the replacement were another 2, staying at index 0 would be necessary to remove it too.
The swap-based method is also O(n) time and O(1) auxiliary space. It can avoid some writes when removals occur near the front, but it has more delicate control flow and changes the order of retained values. Do not choose it because “two pointers” sounds more advanced. Choose it only when order is genuinely irrelevant and the active-end boundary makes the trade useful.
The decision boundary is the contract:
- Choose stable compaction when order or clarity matters.
- Choose swap-with-end when order is unrestricted and rechecking replacements is handled correctly.
Implement the Remove Element Python Solution
The stable implementation keeps the read/write relationship visible:
def remove_element(nums: list[int], val: int) -> int:
write = 0
for read in range(len(nums)):
if nums[read] != val:
nums[write] = nums[read]
write += 1
return write
Every line has a job:
readexamines every original position.- The condition identifies a retained value.
nums[write] = nums[read]places it in the judged prefix.write += 1records one more retained value.return writereports the prefix length.
Because write <= read, the assignment cannot erase an unread element. The loop reads the original value at read before performing the write, and every future input position remains available for inspection.
Notice why len(nums) is not the answer after removals. Python’s list still has its original storage length. The logical result length is write, while the meaningful data is nums[:write].
Dry-Run the Boundary Cases
Pointer bugs become obvious at the boundaries. Check the prefix, not the irrelevant suffix.
Empty array
nums = [], val = 4
The loop runs zero times. The function returns 0 without accessing an index.
Every value is removed
nums = [5, 5, 5], val = 5
Every value is skipped. write remains 0, which correctly describes an empty valid prefix.
No value is removed
nums = [1, 2, 3], val = 5
Each value is written to its original position. write becomes 3, and the array is unchanged.
Removed values at the beginning
nums = [2, 2, 4, 6], val = 2
The first two values are skipped. Then 4 is written to index 0, and 6 is written to index 1. The returned count is 2, and the valid prefix is [4, 6].
Removed values in the middle
nums = [4, 2, 6, 2, 8], val = 2
The valid prefix develops as [4], then [4, 6], then [4, 6, 8]. The function returns 3; the relevant assertion is nums[:3] == [4, 6, 8].
Repeated target values need no special case. read keeps moving through the input while write waits for the next retained value. That separation is the whole mechanism.
Complexity and the Interview Check
The stable algorithm visits each of the n positions once:
- Time:
O(n) - Auxiliary space:
O(1)
The swap-with-end version has the same asymptotic bounds. A replacement may cause the current index to be checked again, but each such recheck also shrinks the active region, so the total work remains linear.
Before coding, run this checklist:
- Identify the judged prefix.
- Decide whether retained order matters.
- Define
writeas the next valid position, not the last valid index. - Scan every input value with
read. - Copy only values that should remain.
- Return
write. - Test an empty array and an all-removed array.
- Ignore everything after
k.
The transferable recognition rule is simple: when elements can be classified independently and the survivors must be packed into an in-place prefix, derive a read pointer and a write pointer. First name the contract, then name the invariant, and only then write the loop.
References
Research updated Sep 5, 2026


