Pattern

Two Pointers

Master the art of using two coordinated indices to solve array and string problems in O(n) time.

Roadmap

  1. What Is This Pattern?
  2. When to Use It
  3. Variant 1: Opposite Direction
  4. Variant 2: Same Direction (Fast/Slow)
  5. Variant 3: Sliding Window Connection
  6. Template Code
  7. Common Problems
  8. Interactive Demo
  9. Complexity Analysis
  10. Key Takeaways
Section 01

What Is This Pattern?

The two pointers technique uses two index variables that traverse a data structure simultaneously. Instead of checking every possible pair with nested loops (O(n²)), the pointers move strategically so you examine the right elements in a single pass.

The Core Idea

Two pointers eliminate redundant comparisons. At each step, you gain enough information to discard part of the search space, allowing one or both pointers to advance.

Sorted array with opposite-direction pointers
L
1
3
5
7
9
11
R
15
→ moves right ← moves left
Brute Force
O(n²)
Nested loops check every pair: n * (n-1) / 2 comparisons
Two Pointers
O(n)
Each pointer moves at most n times: at most 2n steps total
💡

Why does it work? Because the input has structure (usually sorted or has a monotonic property), moving one pointer gives you information about which direction the other pointer should go. This transforms a 2D search space into a 1D traversal.

Section 02

When to Use It

Look for these recognition signals in a problem statement. If you spot one or more, the two pointers technique is likely applicable.

🔍

Sorted Array or List

The input is sorted (or can be sorted without losing information). Sorted order lets pointers make informed directional choices.

🤝

Find a Pair

Find two elements that satisfy a condition: a target sum, a specific difference, or a complementary relationship.

🔄

Compare from Both Ends

Check palindromes, compare characters from front and back, or validate symmetric properties.

🧹

Remove Duplicates In-Place

Modify the array in-place without extra space. A slow pointer marks the write position, a fast pointer scans ahead.

✂️

Partitioning

Rearrange elements based on a condition (move zeros, Dutch National Flag, separate even/odd).

📏

O(1) Space Required

The problem asks for constant extra space. Two pointers work in-place, making them ideal when you cannot use auxiliary data structures.

Section 03

Variant 1: Opposite Direction

Two pointers start at opposite ends and move inward. This is the classic approach for problems on sorted arrays where you want to find a pair that satisfies a condition.

How It Works

The left pointer starts at index 0, the right pointer at the last index. At each step, evaluate the pair and decide which pointer to move based on the result.

// Opposite Direction — Template
int left = 0, right = arr.length - 1;

while (left < right) {
    int current = evaluate(arr[left], arr[right]);

    if (current == target) {
        // Found the answer
        return new int[]{left, right};
    } else if (current < target) {
        left++;   // Need a larger value
    } else {
        right--;  // Need a smaller value
    }
}

Walkthrough: Two Sum II (Target = 10)

Given sorted array [1, 3, 5, 7, 9, 11], find two numbers that sum to 10.

1
L
1
3
5
7
9
R
11
sum = 1 + 11 = 12 > 10 → too large, move R left
2
L
1
3
5
7
R
9
11
sum = 1 + 9 = 10 == 10 → found the pair! Return [0, 4]

Only 2 steps instead of the 15 pair comparisons brute force would require.

🎯

Use cases: Two Sum II (sorted), Container With Most Water, Valid Palindrome, Trapping Rain Water. Any problem where the sorted order lets you reason about whether to increase or decrease a value.

Section 04

Variant 2: Same Direction (Fast / Slow)

Both pointers start at the same end and move in the same direction, but at different speeds. The fast pointer scans through every element while the slow pointer tracks a "write" position or a boundary.

How It Works

The fast pointer explores the array. When it finds an element that meets the condition, the slow pointer advances and the element is placed at that position.

// Same Direction — Template
int slow = 0;

for (int fast = 0; fast < arr.length; fast++) {
    if (condition(arr[fast])) {
        arr[slow] = arr[fast];
        slow++;
    }
}
// slow is now the new length of the processed array

Walkthrough: Remove Duplicates from [1, 1, 2, 2, 3]

Remove duplicate values in-place, returning the new length. Keep one copy of each unique value.

1
S
F
1
1
2
2
3
slow=0, fast=0. First element is always kept. Advance fast.
2
S
1
F
1
2
2
3
arr[1] == arr[0] (both 1) → duplicate, skip. Only fast advances.
3
1
S
2
F
2
2
3
arr[2] != arr[0] (2 != 1) → new unique! slow++, copy arr[fast] to arr[slow].
4
1
S
2
2
F
2
3
arr[3] == arr[1] (both 2) → duplicate, skip.
5
1
2
S
3
2
F
3
arr[4] != arr[1] (3 != 2) → new unique! slow++, copy. Result: [1, 2, 3], length = 3.

Use cases: Remove Duplicates (#26), Remove Element (#27), Move Zeros (#283), and linked list cycle detection (Floyd's algorithm). The slow pointer builds the result while the fast pointer does the scanning.

Section 05

Variant 3: Sliding Window Connection

The sliding window technique is a specialized form of two pointers. Both pointers move in the same direction, but they define the left and right boundaries of a window that expands and contracts.

How They Relate

TWO POINTERS
Pointers can be at any positions. Typically converge (opposite direction) or one chases the other (same direction). Focus on specific elements at each pointer.
SLIDING WINDOW
Two pointers define a contiguous subarray. The window expands (right++) and contracts (left++). Focus on the aggregate of all elements in the window.

Sliding Window Sketch

L
2
5
1
8
R
3
9
4
window = [5, 1, 8, 3], sum = 17
// Sliding Window — uses two pointers as window boundaries
int left = 0, sum = 0;

for (int right = 0; right < arr.length; right++) {
    sum += arr[right];              // expand window

    while (sum > target) {
        sum -= arr[left];           // shrink window
        left++;
    }
    result = Math.max(result, right - left + 1);
}
🔗

Think of sliding window as "two pointers with state." If you only care about individual elements at the pointer positions, use two pointers. If you need to track an aggregate (sum, count, frequency map) of everything between the pointers, use sliding window.

Section 06

Template Code

These are the canonical Java templates. Memorize the structure, then adapt the condition and action for each specific problem.

Template A: Opposite Direction

// Use when: sorted array, find pair, palindrome check
// Invariant: answer lies within [left, right] at all times

int left = 0, right = arr.length - 1;

while (left < right) {
    // Evaluate the current pair
    int current = arr[left] + arr[right];

    if (current == target) {
        // Found: process result
        return new int[]{left, right};
    } else if (current < target) {
        left++;    // Sum too small — need larger left value
    } else {
        right--;   // Sum too large — need smaller right value
    }
}
// No valid pair found

Template B: Same Direction (Fast / Slow)

// Use when: remove duplicates, filter elements, partition
// Invariant: arr[0..slow-1] contains the processed result

int slow = 0;

for (int fast = 0; fast < arr.length; fast++) {
    if (shouldKeep(arr[fast])) {
        arr[slow] = arr[fast];   // Place at write position
        slow++;                  // Advance write position
    }
}
// slow = new length of the result array
return slow;

Template C: Three Pointers (3Sum Pattern)

// Use when: find triplets, reduce to two-pointer subproblem
// Fix one element, then use opposite-direction on the rest

Arrays.sort(arr);

for (int i = 0; i < arr.length - 2; i++) {
    if (i > 0 && arr[i] == arr[i - 1]) continue;  // skip duplicates

    int left = i + 1, right = arr.length - 1;
    int target = -arr[i];

    while (left < right) {
        int sum = arr[left] + arr[right];
        if (sum == target) {
            result.add(Arrays.asList(arr[i], arr[left], arr[right]));
            while (left < right && arr[left] == arr[left+1]) left++;
            while (left < right && arr[right] == arr[right-1]) right--;
            left++; right--;
        } else if (sum < target) {
            left++;
        } else {
            right--;
        }
    }
}
📝

Memorization tip: All three templates share the same skeleton. The only things that change are: (1) where the pointers start, (2) what condition is evaluated, and (3) what happens when the condition is met. Master the skeleton, then fill in the blanks.

Section 07

Common Problems

These are the classic LeetCode problems that use the two pointers pattern. Grouped by variant for focused practice.

Opposite Direction

#167 Two Sum II - Input Array Is Sorted opposite Medium
#11 Container With Most Water opposite Medium
#15 3Sum opposite Medium
#42 Trapping Rain Water opposite Hard
#125 Valid Palindrome opposite Easy

Same Direction (Fast / Slow)

#26 Remove Duplicates from Sorted Array fast/slow Easy
#27 Remove Element fast/slow Easy
#283 Move Zeroes fast/slow Easy
#141 Linked List Cycle fast/slow Easy
📈

Practice order: Start with #125 (Valid Palindrome) and #26 (Remove Duplicates) to build intuition for both variants. Then tackle #167 (Two Sum II) and #11 (Container With Most Water). Once comfortable, attempt #15 (3Sum) and #42 (Trapping Rain Water) for the full challenge.

Section 08

Interactive Demo

Enter a sorted array and a target sum. Watch the opposite-direction two-pointer approach find a pair step by step.

⚙ Two-Pointer Pair Finder



Press "Step →" or "Run All" to begin.
Section 09

Complexity Analysis

Time
O(n)
Space
O(1)

Why O(n) Time?

In both variants, each pointer advances at most n times and never moves backward. The total number of steps across both pointers is bounded by 2n, which simplifies to O(n).

OPPOSITE DIRECTION
Left moves right at most n times. Right moves left at most n times. Combined: at most n total increments before they meet.
SAME DIRECTION
Fast pointer visits each element exactly once: n steps. Slow pointer moves at most n times. Combined: exactly n iterations of the loop.

Why O(1) Space?

Two pointers work directly on the input array. You only need a constant number of variables (the pointer indices plus a few temporaries) regardless of input size. No auxiliary arrays, hash maps, or recursion stacks are needed.

📝

Exception: 3Sum uses two pointers inside a loop over the array, making the total time O(n²). But the two-pointer portion is still O(n) per iteration. Sorting adds O(n log n), but the O(n²) from the outer loop dominates.

Section 10

Key Takeaways