← All Posts
DSA Series · Arrays · Two Pointers · Same Direction

Same-Direction Pointers

The Core Idea

In the same-direction pattern, both pointers start at (or near) the beginning of the array and move in the same direction. One pointer typically moves faster or only moves when a condition is met.

The most common variant is the read/write pattern: a "read" pointer scans every element while a "write" pointer tracks where to place the next valid element. This is the go-to approach for in-place array transformations.

Prerequisites: This page builds on the Two Pointers Overview. If you have not read it yet, start there for the general technique and motivation.

Two Sub-Patterns

Same-direction problems split into two families:

General Template (Read/Write)

int write = 0;
for (int read = 0; read < n; read++) {
    if (shouldKeep(arr[read])) {
        arr[write] = arr[read];
        write++;
    }
}
// arr[0..write-1] contains the result

The write pointer is always at or behind the read pointer. Everything before write is finalized output. Everything between write and read is "garbage" that will be overwritten. Everything after read is unprocessed input.

Problem 1: Remove Duplicates from Sorted Array

Problem: Given a sorted array, remove the duplicates in-place such that each element appears at most once. Return the number of unique elements.

Example: [1, 1, 2, 2, 3, 4, 4] becomes [1, 2, 3, 4, ...] with length 4.

Why Same-Direction Works

Since the array is sorted, all duplicates are adjacent. The write pointer marks the end of the unique portion. The read pointer scans forward. Whenever the read pointer finds a value different from the last written value, we write it and advance the write pointer.

The Write Pointer Invariant

At every step, arr[0..write-1] contains the unique elements seen so far, in order. The read pointer has processed all elements up to its current position.

Step-Through Animation: Remove Duplicates

Array: [1, 1, 2, 3, 3, 4]. Write pointer (green, above) and Read pointer (orange, below).

1 1 2 3 3 4 0 1 2 3 4 5 W R
W=0, R=0. First element is always kept. Click Step.

C++ Solution

int removeDuplicates(vector<int>& nums) {
    if (nums.empty()) return 0;
    int write = 1; // first element is always unique
    for (int read = 1; read < nums.size(); read++) {
        if (nums[read] != nums[write - 1]) {
            nums[write] = nums[read];
            write++;
        }
    }
    return write;
}

Time O(n), Space O(1)

The read pointer visits each element exactly once. The write pointer advances at most n times. No extra memory is used beyond the two index variables.

Problem 2: Move Zeroes

Problem: Given an array, move all zeroes to the end while maintaining the relative order of the non-zero elements. Do it in-place.

Example: [0, 1, 0, 3, 12] becomes [1, 3, 12, 0, 0].

The Read/Write Approach

This is a classic filtering problem: "keep all non-zero elements in order, then fill the rest with zeroes."

An alternative uses swapping instead of copy-then-fill: swap arr[write] and arr[read] whenever arr[read] is non-zero. This avoids the fill step.

Step-Through Animation: Move Zeroes

Array: [0, 4, 0, 5, 0, 3, 7]. Step through the swap variant.

0 4 0 5 0 3 7 0 1 2 3 4 5 6 W R
W=0, R=0. arr[R]=0, skip. Click Step.

C++ Solution (Swap Variant)

void moveZeroes(vector<int>& nums) {
    int write = 0;
    for (int read = 0; read < nums.size(); read++) {
        if (nums[read] != 0) {
            swap(nums[write], nums[read]);
            write++;
        }
    }
}
Common Mistake: Forgetting that the swap variant naturally places zeroes in the vacated positions. No separate fill step is needed. If you use copy instead of swap, you must fill the remaining positions with zeroes after the loop.

The Fast/Slow Pointer Pattern

In the fast/slow pattern, one pointer moves one step at a time (slow) and another moves two steps (fast). This is primarily used for cycle detection and finding the middle element.

Cycle Detection: The Tortoise and Hare

The classic application is detecting cycles in linked lists, but the same concept applies to any sequence where you can "follow" a next-element function. The idea: if there is a cycle, the fast pointer will eventually lap the slow pointer and they will meet inside the cycle.

The Meeting Guarantee

If the fast pointer enters a cycle of length C, it closes the gap to the slow pointer by 1 at each step (fast gains 2, slow gains 1, net difference = 1). So they must meet within C steps after both enter the cycle.

Array Application: Find the Duplicate

Problem (LeetCode 287): Given an array of n+1 integers where each is in [1, n], find the duplicate number. You must use O(1) extra space.

Treat the array as a linked list: the value at index i points to the next index. Since there is a duplicate, there must be a cycle. Use Floyd's cycle detection to find it.

int findDuplicate(vector<int>& nums) {
    // Phase 1: detect cycle
    int slow = nums[0], fast = nums[0];
    do {
        slow = nums[slow];
        fast = nums[nums[fast]];
    } while (slow != fast);

    // Phase 2: find entrance
    slow = nums[0];
    while (slow != fast) {
        slow = nums[slow];
        fast = nums[fast];
    }
    return slow;
}

Step-Through Animation: Fast/Slow on Array

Array: [2, 4, 1, 3, 0] (values represent "next index"). Slow (green) moves 1 step, Fast (orange) moves 2 steps per click. Chain: 0 leads to 2 leads to 1 leads to 4 leads to 0 (cycle).

2 4 1 3 0 0 1 2 3 4 S F
S at index 0, F at index 0. Click Step.

Happy Number (Array-Style Application)

Problem: A number is "happy" if repeatedly summing the squares of its digits eventually reaches 1. If it never reaches 1, it loops forever. Detect if a number is happy.

This is cycle detection in disguise. The "next" function computes the sum of digit squares. Use slow/fast pointers on this sequence:

int digitSquareSum(int n) {
    int sum = 0;
    while (n > 0) {
        int d = n % 10;
        sum += d * d;
        n /= 10;
    }
    return sum;
}

bool isHappy(int n) {
    int slow = n, fast = n;
    do {
        slow = digitSquareSum(slow);
        fast = digitSquareSum(digitSquareSum(fast));
    } while (slow != fast);
    return slow == 1;
}

Problem 3: Partition Array

Problem: Given an array and a pivot value, rearrange elements so that all elements less than the pivot come before all elements greater than or equal to the pivot. Maintain relative order within each group (stable partition).

Read/Write for Partitioning

Use two passes or a single-pass write pointer. For a single-pass stable partition:

int partition(vector<int>& arr, int pivot) {
    int write = 0;
    for (int read = 0; read < arr.size(); read++) {
        if (arr[read] < pivot) {
            swap(arr[write], arr[read]);
            write++;
        }
    }
    return write; // index where >= pivot section begins
}

Relation to Quicksort

This is exactly the partition step in Lomuto's quicksort. Understanding the read/write pointer here gives you a clear mental model for how quicksort's partition works.

Problem 4: Remove Element

Problem: Given an array and a value, remove all instances of that value in-place. Return the new length.

Example: [3, 2, 2, 3], val=3 becomes [2, 2, ...], length 2.

int removeElement(vector<int>& nums, int val) {
    int write = 0;
    for (int read = 0; read < nums.size(); read++) {
        if (nums[read] != val) {
            nums[write] = nums[read];
            write++;
        }
    }
    return write;
}

This is the simplest read/write pointer problem. The "shouldKeep" condition is just "not equal to the target value."

When to Use Same-Direction Pointers

SignalPatternExample
"In-place" + "remove/filter"Read/WriteRemove Element, Move Zeroes
"Sorted" + "duplicates"Read/WriteRemove Duplicates
"Partition" + "rearrange"Read/WriteSort Colors, Partition Array
"Cycle" + "linked list"Fast/SlowLinked List Cycle, Find Duplicate
"Middle element"Fast/SlowMiddle of Linked List
"Converges or loops"Fast/SlowHappy Number

Same Direction vs Opposite Ends

How to Decide

Opposite ends work when you need to combine information from both extremes of a sorted array (pair sums, palindromes, container widths).

Same direction works when you need to process elements sequentially, keeping some and discarding others (filtering, compaction, cycle detection).

If the problem says "in-place" and involves filtering or rearranging, think same-direction. If it says "sorted" and involves finding pairs, think opposite-ends.

Practice Problems

ProblemDifficultyPattern
Remove Duplicates from Sorted Array (LC 26)EasyRead/Write
Remove Element (LC 27)EasyRead/Write
Move Zeroes (LC 283)EasyRead/Write with swap
Remove Duplicates II (LC 80)MediumRead/Write (keep up to 2)
Sort Array By Parity (LC 905)EasyRead/Write partition
Find the Duplicate Number (LC 287)MediumFast/Slow cycle
Happy Number (LC 202)EasyFast/Slow cycle
Linked List Cycle (LC 141)EasyFast/Slow
Middle of Linked List (LC 876)EasyFast/Slow

Summary