Two pointers is the technique behind an entire class of problems where a nested loop feels natural but is actually wasted work — because the data has some structure (usually: it's sorted) that lets you rule out large chunks of the search space just by comparing.
What "two pointers" actually means
Instead of one index scanning the whole array while an inner loop re-scans it again, you keep two indices moving through the data, and you decide which one to move based on a comparison — never both blindly, never re-checking work you've already ruled out. Because each pointer only ever moves forward (never backward, never resets), the total number of steps across the *whole* algorithm is bounded by how far each pointer can travel — O(n), not O(n²).
The three shapes this technique actually takes
- Opposite ends, converging inward. One pointer starts at index 0, the other at the last index; each step moves whichever side the comparison says can't possibly be part of a better answer. This needs the data to be sorted (or the comparison to be meaningful regardless of order, like heights in Container With Most Water). Two Sum II, Valid Palindrome, Container With Most Water, Trapping Rain Water, 3Sum, Squares of a Sorted Array.
- Slow / fast, same direction. Both pointers start together and move the same way, but the slow one only advances when it needs to record something — this is how you compact or filter an array in place without extra space. Remove Duplicates, Move Zeroes.
- One pointer per sequence. Two separate sequences (two arrays, or a string against another string), each with its own pointer, advancing independently based on a comparison between them. Merge Sorted Array, Is Subsequence.
Before coding, name which of the three shapes a problem is. "Sorted array, looking for a pair/triplet with some sum" is shape 1. "Rewrite this array in place, keeping only some elements" is shape 2. "Compare/merge two separate sequences" is shape 3. The shape tells you where the pointers start and what moves them — the code almost writes itself once you've named it.
Why moving the "wrong" pointer would break it
In the opposite-ends shape, the entire correctness argument rests on one claim: whichever side you move away from can be proven to never produce a better answer than what you already have. Container With Most Water makes this concrete:
// The container's water is capped by the SHORTER of the two walls.
// If height[left] < height[right], every container using "left" paired
// with anything between left+1 and right is STILL capped by height[left]
// -- and narrower, so worse. There's no point ever pairing "left" with
// anything closer than "right" again. Moving "right" instead would throw
// away the one candidate (the current "left") that might still pair
// better with something further in -- that's the bug.
if (height[left] < height[right]) ++left; // left is the limiting side -- advance it
else --right;This is the part that actually separates understanding the technique from pattern-matching it: being able to say why the pointer you moved was the correct one to move, not just that the code happens to pass.
Common mistakes
- Using this technique on unsorted data when the opposite-ends shape needs sortedness to be valid — if the problem doesn't guarantee sorted input, you sort first (O(n log n)), which is still better than the O(n²) alternative, but it's an extra step people forget.
- Off-by-one on the stopping condition —
left < rightvsleft <= rightchanges whether the pointers are allowed to land on the same index, which matters when a single middle element needs to be considered on its own (binary search has the exact same trap). - Forgetting to skip duplicates in problems that ask for unique combinations (3Sum) — without it, the same triplet gets added multiple times as the pointers pass over repeated values.
- Merging into a buffer from the front when you should merge from the back (Merge Sorted Array) — writing from the front overwrites values in the destination you still need to read.
Takeaways
- Two pointers turns an O(n²) nested scan into O(n) by moving each pointer at most n times total, based on a comparison — never re-scanning what's already been ruled out.
- Three shapes: opposite ends converging (needs sorted/orderable data), slow/fast same direction (in-place compaction), one pointer per sequence (merging/comparing two sequences).
- The real skill is justifying why the pointer you moved can't produce a better answer — not just getting the code to pass.
- Sorting first (O(n log n)) to unlock the opposite-ends shape is still a huge win over O(n²) — don't skip it just because the input wasn't already sorted.
Try it: opposite ends, converging
Two Sum II is the purest version of this shape — sortedness alone lets you drop the hash map from Arrays & Hashing entirely and still get O(n) time, now in O(1) space.
Loading starter code…
Try it: slow/fast, in place
Move Zeroes is the cleanest version of the slow/fast shape: one pointer scans ahead, the other marks where the next kept value goes.
Loading starter code…
Try it: proving which pointer to move
Container With Most Water is the one where you have to actually justify the move, not just pattern-match it — the walkthrough above is this exact problem.
Loading starter code…
That's 3 of the 12 problems in this topic. 3Sum and Trapping Rain Water are worth doing once these three feel automatic — they're the same shapes, just with an extra layer of reasoning on top. See the full Two Pointers set →
Checkpoint · Two Pointers
5 questions · pass at 70%
Finished Two Pointers?
Pass the quiz to complete it automatically.