LeetCopilot Logo
LeetCopilot
LeetCode Pattern/Two Pointers/Remove Duplicates from Sorted Array: The Write Pointer Pattern Explained

Remove Duplicates from Sorted Array: The Write Pointer Pattern Explained

LeetCopilot Team
Dec 9, 2025
10 min read
Two PointersIn-PlaceArray ManipulationRead Write PatternInterview Prep
The read/write pointer pattern for in-place array manipulation is confusing at first. Learn the intuition, step-by-step walkthrough, and why this pattern works for removing duplicates.

You're solving "Remove Duplicates from Sorted Array" (LeetCode #26). You see the solution:

python
def removeDuplicates(nums):
    if not nums:
        return 0
    
    write = 1
    for read in range(1, len(nums)):
        if nums[read] != nums[read - 1]:
            nums[write] = nums[read]
            write += 1
    
    return write

It works, but the logic feels backwards. Why does write start at 1? Why are we comparing nums[read] with nums[read - 1]? What's actually happening?

This is the read/write pointer pattern—a specific variant of two pointers for in-place array manipulation. Once you understand the intuition, it becomes crystal clear.

This guide will explain the read/write pointer concept, walk through a step-by-step example, and show you how to apply this pattern to similar problems.

TL;DR

  • Read pointer: Scans through the array, examining each element
  • Write pointer: Marks where to place the next unique element
  • Pattern: Read scans ahead, write stays behind, only advancing when we find a new unique element
  • Key insight: We're building a new array in-place, overwriting the old values
  • Return value: The write pointer's final position is the length of the new array

The Intuition: Building a New Array In-Place

The Naive Approach (Extra Space)

If we had extra space, removing duplicates would be simple:

python
def removeDuplicates(nums):
    result = []
    for i, num in enumerate(nums):
        if i == 0 or num != nums[i - 1]:
            result.append(num)
    return result

Example:

code
Input:  [1, 1, 2, 2, 3]
Output: [1, 2, 3]

Simple! But this uses O(n) extra space.

The In-Place Approach (Read/Write Pointers)

The problem requires O(1) space. We must modify the array in-place.

Key insight: We can build the result array in the same array, overwriting values as we go.

The pattern:

  • Read pointer: Scans through the array
  • Write pointer: Marks where to write the next unique value

Visual concept:

code
[1, 1, 2, 2, 3]
 W  R           Read finds duplicate (1 == 1), don't write

[1, 1, 2, 2, 3]
 W     R        Read finds new value (2 != 1), write it

[1, 2, 2, 2, 3]
    W     R     Read finds duplicate (2 == 2), don't write

[1, 2, 3, 2, 3]
       W     R  Read finds new value (3 != 2), write it

Result: [1, 2, 3, ?, ?]
             W
Length = 3 (write pointer position)

Step-by-Step Walkthrough

Let's trace through the algorithm with nums = [1, 1, 2, 2, 3]:

Initial State

python
nums = [1, 1, 2, 2, 3]
write = 1  # Start at index 1
read = 1   # Start at index 1

Why start at 1?

  • The first element (index 0) is always unique (nothing before it to compare)
  • We keep it as-is
  • Start comparing from index 1 onward

Iteration 1: read = 1

code
nums = [1, 1, 2, 2, 3]
        ^  ^
        |  read
        write

Compare: nums[1] (1) vs nums[0] (1)
Same? Yes → Skip (don't write)
write stays at 1

Iteration 2: read = 2

code
nums = [1, 1, 2, 2, 3]
        ^     ^
        |     read
        write

Compare: nums[2] (2) vs nums[1] (1)
Same? No → New unique value!
Write nums[2] to nums[write]:
  nums[1] = nums[2] = 2
  write = 2

nums = [1, 2, 2, 2, 3]
           ^
           write

Iteration 3: read = 3

code
nums = [1, 2, 2, 2, 3]
           ^     ^
           |     read
           write

Compare: nums[3] (2) vs nums[2] (2)
Same? Yes → Skip
write stays at 2

Iteration 4: read = 4

code
nums = [1, 2, 2, 2, 3]
           ^        ^
           |        read
           write

Compare: nums[4] (3) vs nums[3] (2)
Same? No → New unique value!
Write nums[4] to nums[write]:
  nums[2] = nums[4] = 3
  write = 3

nums = [1, 2, 3, 2, 3]
              ^
              write

Final State

code
nums = [1, 2, 3, 2, 3]
              ^
              write = 3

Return write = 3 (length of unique elements)
First 3 elements are the result: [1, 2, 3]

Why This Pattern Works

The Invariant

At any point during the algorithm:

  • nums[0...write-1] contains all unique elements found so far
  • nums[write] is where we'll write the next unique element
  • nums[read] is the current element we're examining

The Key Comparison

python
if nums[read] != nums[read - 1]:

Why compare with nums[read - 1]?

Because the array is sorted:

  • If nums[read] == nums[read - 1], it's a duplicate
  • If nums[read] != nums[read - 1], it's a new unique value

We don't need to check all previous elements—just the one immediately before.

Why Write Pointer Stays Behind

The write pointer only advances when we find a new unique element. This ensures:

  • We don't overwrite unique values we've already placed
  • The write pointer always points to the next available position
  • The final write position is the length of the result

The Complete Implementation

Python

python
def removeDuplicates(nums: List[int]) -> int:
    # Edge case: empty array
    if not nums:
        return 0
    
    # Write pointer starts at 1 (index 0 is always unique)
    write = 1
    
    # Read pointer scans from index 1 to end
    for read in range(1, len(nums)):
        # If current element is different from previous
        if nums[read] != nums[read - 1]:
            # Write it to the write position
            nums[write] = nums[read]
            # Move write pointer forward
            write += 1
    
    # Return the length of unique elements
    return write

JavaScript

javascript
function removeDuplicates(nums) {
    if (nums.length === 0) return 0;
    
    let write = 1;
    
    for (let read = 1; read < nums.length; read++) {
        if (nums[read] !== nums[read - 1]) {
            nums[write] = nums[read];
            write++;
        }
    }
    
    return write;
}

Java

java
public int removeDuplicates(int[] nums) {
    if (nums.length == 0) return 0;
    
    int write = 1;
    
    for (int read = 1; read < nums.length; read++) {
        if (nums[read] != nums[read - 1]) {
            nums[write] = nums[read];
            write++;
        }
    }
    
    return write;
}

Visual Table Trace

Stepreadnums[read]nums[read-1]Different?ActionArray Statewrite
Init-----[1,1,2,2,3]1
1111NoSkip[1,1,2,2,3]1
2221YesWrite[1,2,2,2,3]2
3322NoSkip[1,2,2,2,3]2
4432YesWrite[1,2,3,2,3]3
End----Return 3[1,2,3,?,?]3

Common Mistakes

Mistake 1: Starting Write at 0

python
# WRONG
write = 0
for read in range(len(nums)):
    if read == 0 or nums[read] != nums[read - 1]:
        nums[write] = nums[read]
        write += 1

Why it's wrong: This works, but it's unnecessarily complex. The first element is always unique, so we can skip it.

Better: Start write at 1, skip the first element.

Mistake 2: Comparing with nums[write - 1]

python
# WRONG
if nums[read] != nums[write - 1]:

Why it's wrong: We should compare with the previous element in the original array (nums[read - 1]), not with what we've written (nums[write - 1]).

Mistake 3: Not Returning Write Pointer

python
# WRONG
return len(nums)

Why it's wrong: The length of the array doesn't change. We need to return the number of unique elements, which is the write pointer's position.

Mistake 4: Forgetting Edge Cases

python
# WRONG: Doesn't handle empty array
write = 1
for read in range(1, len(nums)):
    # ...

Fix: Check for empty array first.

Variations of This Pattern

Variation 1: Remove Duplicates II (Allow 2 Duplicates)

python
def removeDuplicates(nums):
    if len(nums) <= 2:
        return len(nums)
    
    write = 2  # Start at 2 (first two are always valid)
    
    for read in range(2, len(nums)):
        # Compare with element 2 positions back
        if nums[read] != nums[write - 2]:
            nums[write] = nums[read]
            write += 1
    
    return write

Key change: Compare with nums[write - 2] to allow up to 2 duplicates.

Variation 2: Move Zeroes

python
def moveZeroes(nums):
    write = 0  # Position for next non-zero
    
    for read in range(len(nums)):
        if nums[read] != 0:
            nums[write] = nums[read]
            write += 1
    
    # Fill remaining positions with zeros
    for i in range(write, len(nums)):
        nums[i] = 0

Key change: Write pointer tracks non-zero elements, then fill rest with zeros.

Variation 3: Remove Element

python
def removeElement(nums, val):
    write = 0
    
    for read in range(len(nums)):
        if nums[read] != val:
            nums[write] = nums[read]
            write += 1
    
    return write

Key change: Skip elements equal to val instead of duplicates.

Time and Space Complexity

Time Complexity: O(n)

  • Single pass through the array
  • Constant work per element

Space Complexity: O(1)

  • Only use two pointers (write and read)
  • Modify array in-place

Practice Strategy

To master the read/write pointer pattern:

  1. Solve Remove Duplicates (#26) - basic pattern
  2. Solve Remove Duplicates II (#80) - allow k duplicates
  3. Solve Move Zeroes (#283) - partition pattern
  4. Solve Remove Element (#27) - remove specific value
  5. Manually trace each solution on paper to internalize the pattern

FAQ

Q: Why does write start at 1 instead of 0?

A: The first element (index 0) is always unique—there's nothing before it to compare. We keep it as-is and start writing from index 1.

Q: What if the array is empty?

A: Return 0. The edge case check handles this.

Q: What happens to the elements after the write pointer?

A: They're ignored. The problem only cares about the first write elements. The rest can be anything.

Q: Can I use this pattern on unsorted arrays?

A: Not for removing duplicates (you'd need a hash set). But you can use it for other in-place operations like moving zeros or removing specific values.

Q: Why not use a hash set to track seen values?

A: That would use O(n) space. This problem requires O(1) space. Also, since the array is sorted, we can detect duplicates by comparing adjacent elements.

Conclusion

The read/write pointer pattern is a powerful technique for in-place array manipulation.

Key concepts:

  • Read pointer: Scans through the array
  • Write pointer: Marks where to place the next valid element
  • Invariant: nums[0...write-1] contains the result so far
  • Return: Write pointer's position is the length of the result

The pattern:

python
write = 1  # or 0, depending on problem
for read in range(1, len(nums)):  # or 0
    if condition:  # element is valid
        nums[write] = nums[read]
        write += 1
return write

Why it works:

  • We're building a new array in-place
  • Write pointer stays behind, only advancing for valid elements
  • Read pointer scans ahead, examining each element
  • Final write position is the length of the result

Master this pattern, and you'll solve all in-place array manipulation problems with confidence. For more on two pointers, see the complete guide and in-place manipulation.

Next time you see "modify array in-place," think: read/write pointers.

Want to Practice LeetCode Smarter?

LeetCopilot is a free browser extension that enhances your LeetCode practice with AI-powered hints, personalized study notes, and realistic mock interviews — all designed to accelerate your coding interview preparation.

Also compatible with Edge, Brave, and Opera

Related Tutorials