← All Posts
DSA Series · Arrays · Two Pointers · Overview

Two Pointers: Overview

What Is the Two Pointers Technique?

The two pointers technique uses two index variables that move through a data structure (usually an array or string) according to certain rules. By maintaining these two references simultaneously, you can often reduce an O(n2) brute-force search to O(n) time.

Think of it like reading a book with two bookmarks. Instead of checking every pair of pages, you advance each bookmark based on what you have already seen. The key insight: the relationship between the two positions lets you skip large portions of the search space.

Two pointers is not a single algorithm. It is a family of patterns that share the idea of tracking two positions and moving them strategically.

Why Does It Work?

In a brute-force approach, checking all pairs in an array of length n requires O(n2) comparisons. Two pointers achieves O(n) because each pointer moves in one direction and never backtracks. Since each pointer visits at most n elements, the total work is at most 2n, which is O(n).

This works when the problem has a monotonic property: moving one pointer in a certain direction guarantees that the optimal position for the other pointer either stays or moves in a predictable direction.

The Monotonicity Principle

If increasing the left pointer can only cause the optimal right to stay or increase, you never need to re-examine earlier positions. This is what makes two pointers linear.

The Three Main Patterns

Most two-pointer problems fall into one of three categories. Understanding which pattern applies is the first step to solving any two-pointer problem.

1. Opposite Ends

One pointer starts at the beginning, the other at the end. They move toward each other until they meet.

When to use: Sorted arrays, palindrome checks, pair-sum problems.

Examples: Two Sum (sorted), Container With Most Water, Valid Palindrome.

2. Same Direction

Both pointers start at (or near) the beginning and move in the same direction, but at different speeds or conditions.

When to use: Removing duplicates, partitioning, read/write pointer problems.

Examples: Remove Duplicates, Move Zeroes, Partition Array.

3. Fast and Slow

One pointer moves one step at a time, the other moves two steps. Used mainly with linked lists.

When to use: Cycle detection, finding the middle element, detecting loops.

Examples: Linked List Cycle, Find Middle, Happy Number.

Visual: How Pointers Move

These animations show two of the main patterns. Step through to see how two pointers traverse an array in each case.

Opposite Ends Pattern

Array: [1, 3, 5, 7, 9, 11, 13]. Left pointer (green) starts at index 0, right pointer (orange) at index 6. They move toward each other.

1 3 5 7 9 11 13 L R
L=0, R=6. Click Step to advance.

Same Direction Pattern

Array: [0, 1, 0, 3, 0, 5, 0]. Write pointer (green) marks where to place non-zero values. Read pointer (orange) scans every element.

0 1 0 3 0 5 0 W R
W=0, R=0. Click Step to advance.

When to Use Two Pointers

Look for these signals in a problem statement:

Two Pointers vs Other Techniques

TechniqueTimeSpaceBest For
Brute Force (nested loops)O(n2)O(1)Small inputs, baseline
Hash MapO(n)O(n)Unsorted arrays, frequency counting
Two PointersO(n)O(1)Sorted arrays, in-place operations
Sliding WindowO(n)O(1) to O(k)Contiguous subarrays of variable length
Binary SearchO(n log n)O(1)Sorted arrays, decision problems

Time and Space Complexity

For all three patterns, the key guarantee is:

Some variants (like 3Sum) nest two pointers inside an outer loop, giving O(n2). But even then, this is an improvement over the O(n3) brute force.

General Code Templates

Here are the skeletons for each pattern in C++:

Opposite Ends Template

int left = 0, right = n - 1;
while (left < right) {
    int current = evaluate(arr, left, right);
    if (current == target) {
        return {left, right};     // found
    } else if (current < target) {
        left++;                   // need larger value
    } else {
        right--;                  // need smaller value
    }
}

Same Direction Template

int write = 0;
for (int read = 0; read < n; read++) {
    if (shouldKeep(arr[read])) {
        arr[write] = arr[read];
        write++;
    }
}
// Elements in [0, write) are the result

Fast and Slow Template

ListNode* slow = head;
ListNode* fast = head;
while (fast != nullptr && fast->next != nullptr) {
    slow = slow->next;          // 1 step
    fast = fast->next->next;    // 2 steps
    if (slow == fast) {
        break;                   // cycle detected
    }
}

Series Roadmap

This series covers the two pointers technique in depth across three focused posts:

  1. Opposite Ends: Two Sum (sorted), Container With Most Water, Valid Palindrome. Pointers converging from both sides.
  2. Same Direction and Fast/Slow: Remove Duplicates, Move Zeroes, Partition, cycle detection concepts. Read/write and fast/slow pointer patterns.
  3. Competition Patterns: 3Sum, Dutch National Flag, Trapping Rain Water. Advanced problems combining multiple patterns.