Skip to content
beginner

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.

Published 2026-09-02Updated 2026-09-128 min read
Professional business meeting with presentation and data analytics on whiteboard.
Professional business meeting with presentation and data analytics on whiteboard. Photo by Mikhail Nilov on Pexels.
Problem

Remove Element

Difficulty: EasyAcceptance rate: 62.4%

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.

ArrayTwo Pointers

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.

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:

  1. Return the count of retained values.
  2. Put those values in nums[0:k].
  3. 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

A compact array trace for [3, 2, 2, 3] with 3 removed: read skips the first 3, write places the two 2 values into positions 0 and 1, and the final 3 is skipped; the resulting valid prefix is [2, 2].
The read pointer classifies every position; the write pointer marks the next destination for a retained value.

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.

  • read visits every original position.
  • write marks 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 to val, and write equals 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:

readwriteActionValid prefix
00Skip 3[]
10Write 2 at index 0[2]
21Write 2 at index 1[2, 2]
32Skip 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: write is 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:

  • read examines every original position.
  • The condition identifies a retained value.
  • nums[write] = nums[read] places it in the judged prefix.
  • write += 1 records one more retained value.
  • return write reports 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:

  1. Identify the judged prefix.
  2. Decide whether retained order matters.
  3. Define write as the next valid position, not the last valid index.
  4. Scan every input value with read.
  5. Copy only values that should remain.
  6. Return write.
  7. Test an empty array and an all-removed array.
  8. 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

  1. Remove One Element to Make the Array Strictly Increasing - LeetCodeleetcode.com
  2. LeetCode 27 Remove Element Solution & Explanation | NeetCodeneetcode.io
8sources checked
8source domains
5searches run

Research updated Sep 5, 2026

Related sites

Strengthen the language foundations behind the solution

Use LearnPyFast and LearnJSFast when you want to reinforce the language mechanics that support interview implementations.

Python tutorialstutorial

LearnPyFast

Beginner-friendly Python tutorials, examples, and learning paths for practical programming foundations.

PythonProgrammingBeginners
Visit LearnPyFast
JavaScript tutorialstutorial

LearnJSFast

Beginner-friendly JavaScript tutorials for practical web development and self-taught developers.

JavaScriptFrontendWeb development
Visit LearnJSFast

Keep grinding

Related coding interview problems

Continue with nearby problems that reuse the same data-structure, invariant, or optimization pattern.

Professional team discussing analytics and brainstorming ideas in a meeting room.
intermediate
12 min read

3Sum Closest

The target does not identify the winning triplet. It tells each pointer which direction is still worth exploring.

View solution
Close-up of hands coding on a laptop, showcasing software development in action.
intermediate
10 min read

3Sum

A reliable 3Sum solution comes from turning a cubic search into a sequence of sorted two-sum scans—and proving why each pointer move is safe.

View solution
Visual abstraction of neural networks in AI technology, featuring data flow and algorithms.
advanced
13 min read

4Sum

Four choices suggest an O(n^4) search. Sorting changes the last two choices into a controlled walk.

View solution