{} Coding Interviews · ch.05 · two pointers
🧩 Part II · Linear Structures · chapter 5 / 24

Start at both ends,
retire a suspect every step

On sorted data, position is information: one finger at each end, and every comparison proves a whole batch of candidate pairs innocent. One skeleton, five costumes — and the O(n²) inner loop quietly disappears.

Chapter 4's hash map bought speed with memory: pay O(n) space and "have I seen this?" becomes free. This chapter's trick costs nothing — if the data is sorted, its positions already answer questions. Two index fingers, one at each end, walking toward each other: that's the whole pattern, and it kills quadratic loops for pair sums, palindromes, water containers, and 3Sum.

The skill to take from this chapter is a single sentence — the discard argument — that justifies every pointer move. Say it out loud in an interview and you've demonstrated the exact "reasons about invariants" signal Chapter 1 said they're buying.

1Two fingers on one array — the converging walk

Meet Two Sum II Medium: same deal as Chapter 4's Two Sum — find two numbers that hit a target — except the array arrives sorted, and the problem demands O(1) extra space. That second clause is the problem legally forbidding the hash map. It's not being cruel; it's steering you to the better tool.

The tool: plant l = 0 on the smallest value and r = n − 1 on the largest. Look at a[l] + a[r]:

  • Sum equals target — done. Collect your indices and your offer letter.
  • Sum too small — you need more; only the left finger can supply it. l++.
  • Sum too big — you need less; only the right finger can shed it. r--.

Loop while l < r. Each step moves exactly one pointer inward, so the walk takes at most n − 1 steps: O(n) — one pass, no allocations, no map. The mystery worth a section of its own: why is it safe to move a pointer and never look back?

🎯
The tell: sorted (or cheaply sortable) input + "find a pair/triple that hits a target condition" → two pointers, before anything else. If the array is unsorted and the indices must survive, Chapter 4's hash map bids instead — Chapter 23 referees the tie.

2Why moving on is safe — the discard argument

Ten elements have 45 possible pairs. Brute force interrogates all 45. The converging walk asks at most 9 questions — so each question must be eliminating batches of pairs, not just the one it looked at. Here's the argument, and it's the sentence to say out loud in the room:

"If a[l] + a[r] < target, then a[l] just failed with the biggest partner it will ever get. Failing with your best partner means failing with everyone — a[l] cannot appear in any answer. Retire it."

Symmetrically, when the sum overshoots, a[r] just overshot with the smallest partner still available, so it's out. One comparison, and every pair involving the retired element is ruled out at once — a whole row of the pair table gone. That's the entire reason sortedness matters: it makes the ends extreme, and extremes make one test speak for many.

Drag the pointers below anywhere you like — the explainer will tell you which move the algorithm would force, and why. Watch the "pairs ruled out" counter: it's doing 45 pairs of work in single-digit moves.

Interactive · converging pointers Drag L / R or press step — each move rules out a whole batch of pairs
L = 0 · R = 9
29
a[L] + a[R]
verdict
searching…
pointer moves
0
pairs ruled out
0 / 45
💡
Sortedness is fuel, and two patterns burn it differently. Binary search (Chapter 9) halves the space per test and finds a position in O(log n); two pointers shaves one element per test and finds a pair in O(n). Same gift, different questions — Chapter 3 called sorted input a gift for exactly this reason.

3Meet in the middle — the mirror walk

Swap the question from sum to compare and the same skeleton solves Valid Palindrome Easy — the warm-up interviewers reach for when they want you talking within two minutes. A string reads the same backward exactly when every mirrored pair of characters matches, and the two fingers are the mirror: compare s[l] with s[r], step both inward, first mismatch ends it.

The interview version adds grit: "ignore non-alphanumeric characters and case." That's two tiny inner loops — while s[l] isn't alphanumeric, l++; same on the right — then lowercase both and compare. Each character is still visited once, so it stays O(n) with O(1) space, which is the entire point: the "reverse the string and compare" one-liner also works but silently spends O(n) memory, and a good interviewer will ask you why.

The follow-up they love: Valid Palindrome II — "you may delete at most one character." At the first mismatch you get a fork: skip the left char or skip the right char, and check if either remaining slice is a clean palindrome. Two extra walks, still linear. Note the shape: the pattern didn't change, the question at the fingertips did.

🎯
The tell: "reads the same backward", "is a mirror of", "symmetric around the center" → converge-and-compare. (On a linked list, where you can't index from the back, Chapter 10's fast-slow pointers find the middle first — same instinct, different vehicle.)

4The widest container — a greedy proof you can watch

Container With Most Water Medium: vertical walls of given heights; pick two that hold the most water. Area = min(h[l], h[r]) × (r − l) — the shorter wall sets the ceiling. Brute force tries all pairs, O(n²) — "for each wall, try every other wall". The two-pointer version starts at maximum width and walks inward in O(n), with one rule: always retire the shorter wall.

Why is that safe? The discard argument again, in greedy clothing. Once the pointers start moving inward, width only shrinks. So consider the shorter wall: every container it could form from here on is narrower than the current one, and its ceiling can never rise above the short wall itself. The current area is therefore the best that wall will ever do — it has peaked, on camera, and can be retired without regret. The taller wall still has upside (a taller partner might appear), so it stays.

Note what this is: a greedy choice with a one-sentence proof. Chapter 19 makes a whole chapter of that move — and of the disasters that follow when the sentence doesn't actually hold.

Interactive · the container maximizer Step the pointers inward — the proof narrates every retirement
step 1 / 9
area now
best so far
0
areas computed
0
brute force would
36
⚠️
The trap: the plausible wrong greedy — "move whichever pointer gives the bigger area next step". That's a heuristic, not a proof, and it misses optima that sit several steps past a temporary dip. The correct rule never looks ahead; it only retires a wall that provably cannot improve. If you can't say the proof sentence, you don't have a greedy — you have a guess.

53Sum — fix one, two-point the rest

3Sum Medium is the pattern's most famous costume: find all unique triples summing to zero. The leap from pair to triple is one sentence: fix an anchor, and the rest is a pair problem you already solved. Sort once — O(n log n), sort once then walk — then for each anchor a[i], run Section 1's converging walk on the suffix i+1 … n−1 hunting a pair that sums to −a[i].

Cost: n anchors × an O(n) walk = O(n²), and the sort disappears into the noise. Two free accelerations fall out of sortedness: the anchor only needs to run while a[i] ≤ 0 (three positives can't sum to zero — once anchors go positive, stop the whole search), and the walk only ever looks rightward of the anchor, so no triple is examined twice.

Could you use Chapter 4's hash map for the inner pair instead? Yes — and interviewers watch candidates try, because the follow-up "…all unique triples" turns the hash version into a swamp of tuple-canonicalization. The sorted walk dedups almost for free, which is the next section. This is why 3Sum is beloved as an interview stage: it tests whether you can compose two patterns and handle the seam.

6The dedup dance — three skips, zero duplicates

Sorted duplicates sit next to each other, so skipping them is a pointer hop, not a set lookup. The dance has exactly three moves:

  • Skip duplicate anchors. If a[i] == a[i−1], this anchor can only rediscover the previous anchor's triples. continue.
  • After recording a triple, skip duplicate seconds: while a[l] == a[l−1], keep moving l.
  • …and duplicate thirds: while a[r] == a[r+1], keep moving r.

Why only after a record? Because ordinary comparisons already slide over duplicate runs harmlessly — a repeated value just fails the same comparison again and moves on. Duplicate output is only ever born at the moment of success: same anchor re-run, or the same (second, third) pair rediscovered one slot over. Guard the successes and the output is clean.

Step the real algorithm below, then flip dedup off and run it again — same array, same walk, and watch the output fill with clones.

Interactive · the 3Sum dedup stepper Toggle the skip rules and watch duplicates flood the output
state 1 / —
triples recorded
0
duplicates
0 skipped
unique answer
4 triples
⚠️
The trap: dodging dedup by dumping tuples into a set "works", but it announces that you didn't see the structure — and on a whiteboard it costs you the O(1)-space claim for the walk. Second trap: writing the skip loops without the l < r guard, which happily walks the pointers past each other on an array of nine zeros. Interviewers keep an array of nine zeros in a drawer for exactly this.

7Same direction, different jobs — the read/write pair

Not all two-pointer walks converge. The pattern's other body plan puts both pointers at the start, moving the same way, with different jobs: read scans every element; write marks the end of the kept prefix. The invariant to say out loud: everything left of write is the answer so far. When read finds a keeper, copy it to write and advance both; otherwise only read moves. One pass, in place, nothing allocated.

That's the whole solution to Remove Duplicates from Sorted Array Easy (keep an element when it differs from its left neighbor) and Move Zeroes Easy (keep non-zeros, then zero-fill the tail). The problems feel like deletion; the pattern reframes them as compaction — you never delete, you overwrite forgettable things with keepable things.

File the shape carefully, because Chapter 6 is its glow-up: a sliding window is two same-direction pointers plus state maintained between them. Learn the read/write pair today and the caterpillar tomorrow is just this plus a bookkeeping map.

🎯
The tell: "do it in place", "O(1) extra space", "return the new length" → the read/write pointer pair. The phrase "new length" is practically a confession: the answer is wherever write ends up.

8One skeleton, five costumes — the family portrait

Every problem in this chapter is the same skeleton wearing a different question at the fingertips:

  • Converge on a sum — Two Sum II Medium. Question: does a[l] + a[r] hit the target?
  • Converge and compare — Valid Palindrome Easy. Question: do the mirror characters match?
  • Converge greedily — Container With Most Water Medium. Question: which end has provably peaked?
  • Anchor + converge — 3Sum Medium. A loop of walks, plus the dedup dance.
  • Read/write, same direction — in-place compaction. Question: is this element a keeper?

And one preview from the boss room: Trapping Rain Water Hard. Water above cell i is min(maxLeft, maxRight) − h[i] — and a converging walk can carry both running maxes, moving the side with the smaller max, because that side's water level is already decided by its own max. It's this chapter's discard argument playing in the rain. (Chapter 8's monotonic stack will claim the same problem — good problems have shared custody.)

The pattern, as a whiteboard skeleton:

  1. 1Sorted? If not, sort — O(n log n) — and note out loud that original indices won't survive (if they must, rethink: ch. 4).
  2. 2Plant the fingers: l = 0, r = n − 1. For triples: anchor loop outside, walk on i+1 … n−1.
  3. 3Loop while l < r; combine the ends — sum, comparison, or area.
  4. 4Hit? Record or return. Uniqueness required? Skip duplicate anchors, seconds, and thirds — after each record.
  5. 5Miss? Under target → l++; over target → r-- — and say the discard sentence: "failed with its best possible partner."
  6. 6In-place flavor: same-direction read/write pair; the invariant is "left of write = answer so far".
  7. 7Complexity check: each walk is O(n), O(1) space; an anchor loop makes it O(n²).
  8. 8Test on the nasty three: empty/one-element input, all-duplicates array, no-solution case.

93Sum, composed — one sort, one anchor, one walk

The flagship, in full: sort, anchor loop with the positive-anchor early exit, the converging walk from Section 1, and all three moves of the dedup dance. Read it as three stacked patterns — the sort buys the discard argument, the anchor buys the third dimension, the skips buy uniqueness. This is the version to reproduce on a whiteboard; every line has a spoken sentence attached.

Flip the tabs: the Scala version keeps the same imperative skeleton on purpose — two pointers is the rare pattern where the mutable walk is the idiom, and dressing it in folds obscures the invariant you're being paid to narrate. The delta worth memorizing is small: anchor loop, walk, three skips. Everything else is 3Sum-specific color.