๐Ÿณ IntuitiveFE
Login
โ† All concepts

Two Pointers

โฑ๏ธ ~3-minute bite ยท solve the sandbox to master

0%lesson
๐Ÿง’

5-Year-Old Metaphor

โ€” The physical, real-world picture. No jargon.

๐Ÿชฅ Squeezing a tube of toothpaste from both ends toward the middle.

The naive approach (what everyone writes first)

You have a sorted array [1, 3, 5, 7, 9, 11] and want to find two numbers that add to 12. The brute-force approach: pick every pair โ€” check (1,3), (1,5), (1,7) โ€ฆ then (3,5), (3,7) โ€ฆ That's nยฒ pairs for an O(nยฒ) solution.

The squeeze trick (the aha moment)

Because the array is sorted, you can start at both ends and squeeze inward. Sum too big? Move the right pointer left (smaller number enters). Sum too small? Move the left pointer right (bigger number enters). The answer must lie between the pointers โ€” or it doesn't exist.

[1, 3, 5, 7, 9, 11] target = 12
Lโ†‘ โ†‘R sum = 1+11 = 12 โœ“ found!

The key invariant

After every pointer move, you eliminate an entire slice of the search space, not just one pair. Moving the left pointer right discards every pair that could form with the old left value. Because the array is sorted you know none of those pairs can be the answer โ€” they'd all be smaller. Each step eliminates an entire row of the nยฒ grid.

This collapses O(nยฒ) to O(n) โ€” both pointers together traverse the array at most once.

When to reach for two pointers

  • Array is sorted (or can be sorted)
  • Problem involves a pair or triplet summing to a target
  • "Remove duplicates in-place", "partition", or "reverse in-place" โ€” slow/fast pointer variant
  • Palindrome check, water trapping โ€” symmetric squeeze

โš ๏ธ Pitfall: if the array is not sorted and sorting is not allowed, two pointers won't work โ€” use a hash map instead (O(n) time, O(n) space).

๐ŸŽ›๏ธ

Interactive Sandbox

โ€” Move something, see it react instantly.
How to use: pick a pattern below, then step through the pointer moves. Explore all 5 patterns to master this concept.

๐ŸŽฏ Two Sum (Sorted)

target = 12
1
L
3
1
5
2
7
3
9
4
11
R
1 + 11 = 12 โœ“
MATCH โœ“

Start: L=0 (1), R=5 (11). Sum=12. Found it!

Step 1 / 1
Insight: Sum < target โ†’ move left pointer right (increase sum). Sum > target โ†’ move right pointer left (decrease sum). Sum = target โ†’ found. Invariant: the answer lies between the pointers or doesn't exist.
๐ŸŽฏ

Challenge

Explore all 5 two-pointer patterns. (1/5 explored)

Try it
โš ๏ธ Gotcha: Only works on a sorted array. If unsorted, sort first (O(n log n)) or use a hash map (O(n)).
๐ŸŽฏ

Why Should I Care?

โ€” The exact interview question + the bug it kills.

Q โ€” Why must the array be sorted?

The pointer-move decision relies on knowing which direction changes the sum. On a sorted array: moving the left pointer right always increases the sum; moving the right pointer left always decreases it. Without sorted order, neither direction is predictable โ€” you can't eliminate anything, and you're back to O(nยฒ).

Q โ€” How does 3Sum reduce to 2Sum?

Sort the array first. Then iterate: for each element at index i (the anchor), run two-pointer on the rest of the array looking for a pair that sums to -arr[i]. The outer loop is O(n); the inner two-pointer is O(n) per anchor โ†’ total O(nยฒ) vs naive O(nยณ). Skip duplicate anchors to avoid duplicate triplets.

Q โ€” Two pointers vs hash table: when to pick which?

AspectTwo PointersHash Map
Requires sorted inputYes (or sort it)No
Extra spaceO(1)O(n)
TimeO(n) after sortO(n)
In-place mutationsYesNo

Rule of thumb: if the input is already sorted or the constraint is "O(1) space", use two pointers. If unsorted and space is fine, hash map is simpler.

Bug โ€” Off-by-one when skipping duplicates

js
1// After finding a match in 3Sum, skip duplicates:
2while (left < right && arr[left] === arr[left + 1]) left++;
3while (left < right && arr[right] === arr[right - 1]) right--;
4left++;
5right--;
6// BUG: if you forget the left < right guard, you can
7// overshoot and read out of bounds or miss a triplet.

Classic interview question (Two Sum on sorted array)

ts
1function twoSum(numbers: number[], target: number): [number, number] {
2 let left = 0, right = numbers.length - 1;
3 while (left < right) {
4 const sum = numbers[left] + numbers[right];
5 if (sum === target) return [left + 1, right + 1]; // 1-indexed
6 if (sum < target) left++;
7 else right--;
8 }
9 return [-1, -1]; // no solution (won't happen per problem guarantee)
10}
11// Time: O(n) ยท Space: O(1)
๐Ÿ”ฌ

The Deep Dive

โ€” Spec refs, engine internals, the minutiae.

Two flavors of two pointers

Left / Right (squeeze)

  • Start at opposite ends, move inward
  • Requires sorted (or symmetric) input
  • Uses: Two Sum, 3Sum, palindrome, trapping rain
  • O(n) time, O(1) space

Slow / Fast (runner)

  • Both start at the same end; fast races ahead
  • Works on unsorted arrays and linked lists
  • Uses: remove duplicates, Floyd's cycle detection
  • O(n) time, O(1) space

Floyd's cycle detection (slow/fast on linked list)

The classic linked-list cycle problem: is there a loop? Slow pointer moves one step, fast pointer moves two. If they ever point to the same node, there's a cycle. If fast hits null, the list is acyclic.

ts
1function hasCycle(head: ListNode | null): boolean {
2 let slow = head, fast = head;
3 while (fast !== null && fast.next !== null) {
4 slow = slow!.next;
5 fast = fast.next.next;
6 if (slow === fast) return true;
7 }
8 return false;
9}
10// The "meeting point" can also pinpoint the cycle start โ€”
11// reset one pointer to head, advance both by 1 until they meet.

Why it works: the fast pointer laps the slow one inside the cycle. The gap closes by 1 each iteration โ†’ they must meet in O(n).

Multiple pointers (3+ pointers)

Some problems generalise naturally: "4Sum" runs two-pointer inside a double outer loop (O(nยณ)). "Dutch National Flag" (sort array of 0s, 1s, 2s) uses three pointers โ€” low, mid, high โ€” partitioning in a single pass.

ts
1// Dutch National Flag โ€” O(n), O(1)
2function sortColors(nums: number[]): void {
3 let low = 0, mid = 0, high = nums.length - 1;
4 while (mid <= high) {
5 if (nums[mid] === 0) {
6 [nums[low], nums[mid]] = [nums[mid], nums[low]];
7 low++; mid++;
8 } else if (nums[mid] === 1) {
9 mid++;
10 } else { // 2
11 [nums[mid], nums[high]] = [nums[high], nums[mid]];
12 high--; // don't increment mid โ€” the swapped element is unexamined
13 }
14 }
15}

Complexity comparison across variants

ProblemVariantTimeSpace
Two Sum (sorted)Left/RightO(n)O(1)
3SumLeft/Right ร— loopO(nยฒ)O(1)*
Remove DuplicatesSlow/FastO(n)O(1)
Trapping Rain WaterLeft/RightO(n)O(1)
Cycle DetectionSlow/FastO(n)O(1)

* O(log n) or O(n) if you count the sorting step for 3Sum

Related patterns

  • Sliding Window โ€” sibling pattern; left/right on unsorted arrays, constraint-driven instead of sum-driven
  • Binary Search โ€” also exploits sorted order but O(log n) per query, not O(n) end-to-end
  • Monotonic Stack โ€” for "next greater element" style problems where two-pointer would need a sorted invariant it can't guarantee
  • Prefix Sums โ€” when you need arbitrary range sums, not just a moving window
๐ŸŽค

Interview Questions

โ€” Real questions from real interviews โ€” with answers.

Array must be sorted (or have a monotonic property) so moving a pointer predictably changes the sum/product.

Two pointers is O(1) space but requires a sorted array; hashmap is O(n) space but works unsorted.

Skip duplicate values for the outer loop and for both inner pointers after a match.

Slow pointer moves 1 step, fast moves 2 โ€” if a cycle exists they must eventually land on the same node.

Track left-max and right-max as you converge inward โ€” water at each bar is min(leftMax, rightMax) - height[i].

๐ŸŽฎ

Memory Game

โ€” Quick quiz โ€” lock the concept in long-term memory.
1/4

What is the time complexity of 3Sum using sorting + two-pointer?