Pattern GuideThe Two Pointers Pattern
Algorithm Pattern

The Two Pointers Pattern

"Move based on clues, discard the impossible."

14 min read
Fundamental
Time: O(N)
01
THE CORE INTUITION

Stop Scanning Everything

🎯

Imagine you have a large list of numbers and you need to find two numbers that sum exactly to a target value. A naive approach is to fix the first number, then write a second loop to scan every single other number to see if it matches. This forces the computer to make massive amounts of duplicate checks, resulting in terrible performance.

The Two Pointers pattern solves this by introducing a second index that moves independently but strategically. Instead of looking at one item and scanning everything else, you look at two items simultaneously. Depending on the values you see, you move one or both pointers directly towards the answer based on a simple set of logical rules.

THE GENIUS OF TWO POINTERS
Instead of using a slow, nested loop to check every possible combination (O(N²) Time), we use two indices to cleverly move through the structure—converging from opposite ends, racing at different speeds, or zipping two arrays together—to complete the job in one pass (O(N) Time) while mutating data completely in-place (O(1) Space).

The Four Distinct Flavors

Flavor 1

The Squeeze (Opposite Ends)

Start at the boundaries and move inward based on conditional logic until the pointers meet in the middle.

🎯 Perfect for: Sorted Arrays, Palindromes, Seeking Pairs
Flavor 2

The Scanner (Same Direction)

One pointer races ahead to "scan" for good data, while a "slow" pointer trails behind acting as the "writer"/placer.

🎯 Perfect for: In-place filtering, Removing duplicates
Flavor 3

The Racer (Fast & Slow)

Both pointers move in the same direction, but at different speeds (usually 1x and 2x). Used to find loops or midpoints.

🎯 Perfect for: Cycle Detection, Finding Middle Elements
Flavor 4

The Zipper (Merging)

Pointers start on two different arrays or lists, moving independently to merge them together.

🎯 Perfect for: Merging Sorted Arrays/Lists, Set Intersections

When to Unleash Two Pointers

🐢

Nested Loop Trap?

If your code relies on an i inner loop and a j outer loop (O(N²)), Two Pointers can often squash it to O(N).

📈

Is the array sorted?

Sorted arrays are a dead giveaway for "Squeeze" Two Pointers, like finding two numbers that sum to X.

↔️

Palindromes & Symmetry?

Comparing character i with character len - 1 - i is inherently a Two Pointers concept.

💾

O(1) Space Requirement?

If the problem demands you use NO extra space (like HashMaps), you must manipulate data in-place via Two Pointers.

02
DEEP DIVE

Visualizations & Blueprints

1. The Squeeze — Opposite Ends

Closing the search space by moving based on Sum

Sorted Array: [1, 2, 4, 6, 8, 10]
Target: 10
LEFT
1
1
2
2
4
3
6
4
8
RIGHT
10
Current Check
1 + 10 = 11
1 + 10 = 11 > 10 → sum too big → move the RIGHT pointer inward.
1 / 3

Strategy Blueprint

HOW IT WORKS

  • 1. Position left pointer at 0, right pointer at end.
  • 2. Execute a while (left < right) loop.
  • 3. Compare the pair against the target: sum too small → move left right; sum too big → move right left.

When to use:

  • Sorted Array pair searching (Two Sum).
  • Palindrome verification.
  • Maximizing container area/volume.
Concrete Example — Two Sum II
twoSumSorted.js
1
function twoSumSorted(numbers, target) {
2
let left = 0, right = numbers.length - 1;
3
4
while (left < right) {
5
const sum = numbers[left] + numbers[right];
6
if (sum === target) {
7
return [left, right]; // Found the pair
8
} else if (sum < target) {
9
left++; // Need a bigger sum
10
} else {
11
right--; // Need a smaller sum
12
}
13
}
14
return [-1, -1]; // No pair found
15
}
Generalized Skeleton
converging.js
1
let left = 0, right = arr.length - 1;
2
let result = null; // 1. Track best result
3
4
while (left < right) {
5
// 2. Process pair or collect result
6
let current = process(arr[left], arr[right]);
7
result = update(result, current);
8
9
// 3. Eliminate the bottleneck to find a better answer
10
if (eliminateLeft(current)) {
11
left++;
12
} else if (eliminateRight(current)) {
13
right--;
14
} else {
15
left++;
16
right--;
17
}
18
}
19
20
return result;

2. The Scanner — Same Direction

Filtration: Moving useful data to the front (Move Zeroes)

SLOW
0
1
0
3
12
Scanner Strategy

Start: slow and fast both begin at index 0.

1 / 7

Strategy Blueprint

HOW IT WORKS

  • 1. Initialize simple slow pointer at 0.
  • 2. Use fast pointer iteratively in a normal loop.
  • 3. If fast finds target rule, swap data with slow, then increment slow.

When to use:

  • Removing duplicates in-place.
  • Moving elements (e.g., zeroes) to one end.
  • Cleaning up "trash" values while scanning.
sameDirection.js
1
// 1. Slow pointer acts as the 'write' or 'safe' index
2
let slow = 0;
3
4
// 2. Fast pointer scans through the elements
5
for (let fast = 0; fast < arr.length; fast++) {
6
7
// 3. Filter criteria: Keep valid/unique elements
8
if (isValid(arr[fast])) {
9
10
// 4. Place valid item at the slow index and advance
11
swap(arr, slow, fast);
12
slow++;
13
}
14
}
15
16
// Result is often the length of the valid prefix
17
return slow;

3. The Racer — Fast & Slow

Cycle Detection: Fast moving twice as fast

Racer Strategy

Both pointers start together at the head.

Perfect for: Linked List cycles and finding the middle element.

1 / 7

Strategy Blueprint

HOW IT WORKS

  • 1. Start both slow and fast pointers at the beginning.
  • 2. Loop while fast and fast.next are not null.
  • 3. Advance slow by 1 and fast by 2.

When to use:

  • Cycle Detection in Linked Lists/Arrays.
  • Finding the Middle Node of a list.
  • "Happy Number" logic.
fastSlow.js
1
let slow = head;
2
let fast = head;
3
4
// Fast moves twice as quickly
5
while (fast !== null && fast.next !== null) {
6
slow = slow.next;
7
fast = fast.next.next;
8
9
// Cycle detected!
10
if (slow === fast) return true;
11
}
12
return false; // Reached the end (no cycle)

4. The Zipper — Merging

The Zipper: Merging two sorted structures

Arr 1:
1
3
5
Arr 2:
2
4
6
Zipper Strategy

Compare the heads of two structures. Take the smaller one and move only its pointer forward.

Efficiency: Merges two lists in a single O(N+M) pass.

Strategy Blueprint

HOW IT WORKS

  • 1. Initialize pointer1 for structure 1 and pointer2 for structure 2.
  • 2. While both have elements, compare them and pick the winner.
  • 3. When one runs out, greedily take the rest of the other.

When to use:

  • Merging Sorted Arrays/Lists.
  • Finding intersections of two sets.
  • Zipping two data streams together.
merging.js
1
let i = 0, j = 0;
2
let merged = [];
3
4
// Compare elements from both arrays
5
while (i < arr1.length && j < arr2.length) {
6
if (arr1[i] <= arr2[j]) {
7
merged.push(arr1[i++]);
8
} else {
9
merged.push(arr2[j++]);
10
}
11
}
12
13
// Append any remaining elements
14
while (i < arr1.length) merged.push(arr1[i++]);
15
while (j < arr2.length) merged.push(arr2[j++]);
16
17
return merged;
03
CAUTION

Common Pitfalls

Forgot to Sort?

Converging pointers logic (moving based on sum) requires sorted data. If you move pointers based on values in an unsorted array, your logic will likely be wrong.

Off-by-One

Use while (left < right) for pairs, but while (left <= right) if you need to process the middle element (like reversal).

Array Unsorted and Can't Be Sorted?

If you need the original indices or aren't allowed to reorder, Two Pointers doesn't apply — reach for a hash map instead (O(N) time, O(N) space) for pair-sum problems.

Duplicate Triplets in 3Sum

After fixing the first number and squeezing the rest, skip repeated values for all three indices — otherwise the same triplet gets recorded multiple times.

04
PRACTICE

Ready to Practice with Visual Simulations?

Two Sum II — Input Array Is SortedMedium
Valid PalindromeEasy
3SumHard
Container With Most WaterMedium
Trapping Rain WaterHard
Move ZeroesEasy

"Initialize, compare, and move strategically to eliminate work."