Two Pointers

Scan a sorted array or string from both ends at once to find pairs, remove duplicates, or reverse data in O(n).

What is two pointer?

Two pointer uses two index variables — usually called left and right, or slow and fast — that move through an array or string independently. Instead of nesting one loop inside another and re-checking pairs you've already ruled out, each pointer only ever moves forward, so the whole pass stays O(n).

Pointer
An index variable marking one position in the array
Converging
left and right start at opposite ends and move toward each other
Same-direction
Both pointers start together and move forward at different speeds

Why two pointers at all?

Picture a hallway of numbered doors. Two colleagues start at opposite ends and walk toward each other, each one checking the number on their own door and comparing notes as they pass.

Neither of them ever backtracks, and they meet somewhere in the middle. Compare that to one person checking every door against every other door — two pointers turn that into a single walk down the hallway.

Two colleagues closing in from both ends
Left1Right7

Every step closes the gap. They never cross the same door twice.

What kinds of problems does it solve?

Four common shapes. The pointers always move forward — you only change what makes them step, and whether they start apart or together.

Pair that matches a target

On a sorted array, start left and right at opposite ends. If the pair adds up to too little, move left in. Too much, move right in. The sortedness tells you which side is wrong.

Prices sorted · target = 9
Sum10Verdicttoo big

Sum too big → pull the right pointer in. Sum too small → push the left pointer out.

Remove duplicates in place

fast scans every element. slow only advances — and writes — when fast finds something worth keeping. slow ends up marking the new length.

Sorted values · write over the duplicates
fast sees1Actionskip

slow only moves — and writes — on a new value.

Palindrome check

Compare the outermost characters and step both pointers inward. One mismatch means it's not a palindrome — you can stop right there without checking the rest.

Checking "level" from both ends
Comparingl · lMatchyes

Every pair matches until the pointers meet — it's a palindrome.

Merge two sorted lists

One pointer per array. At each step, take whichever front value is smaller and advance only that pointer — the other array's progress is untouched.

Two sorted hands · one pointer each
Comparing1 · 2TakeA

Smaller front wins. Only that pointer moves.

Two types

Underneath, it's really only two shapes: pointers that start apart and close in, and pointers that start together and drift apart at different speeds.

Converging

left starts at 0, right starts at the last index. Every step moves exactly one of them inward, based on a comparison — never both, never outward. They stop when they meet or cross.

left = 0 · right = length − 1
Range[0..4]

One side steps in at a time. Never both, never back out.

converging-pointers.tsTypeScript
function converging(arr: number[], target: number): [number, number] | null {
  let left = 0;
  let right = arr.length - 1;

  while (left < right) {
    const sum = arr[left] + arr[right];
    if (sum === target) return [left, right];
    if (sum < target) left++; // too small, need a bigger sum
    else right--; // too big, need a smaller sum
  }

  return null;
}

Same-direction

slow and fast both start near the front. fast always steps forward and does the scanning. slow only steps — and writes — when fast finds something worth keeping.

slow trails fast, only moves when needed
slow0fast0

fast always advances. slow only catches up when it has to.

same-direction-pointers.tsTypeScript
function sameDirection(arr: number[]): number {
  let slow = 0;

  for (let fast = 0; fast < arr.length; fast++) {
    if (/* arr[fast] should be kept */ true) {
      arr[slow] = arr[fast];
      slow++;
    }
  }

  return slow; // new length
}

Where it works — and where it breaks

Converging pointers lean on a quiet assumption: the array is ordered enough that comparing the two ends tells you, with certainty, which side to move. Break that ordering and the same move can walk right past the answer.

Works on a sorted array

Sum too big only ever means the right value is too big — moving right in is provably correct. Sum too small only ever means the left value is too small. The order guarantees the move.

Sorted · target = 9 · pointers close in correctly
Sum9Verdictmatch

Breaks on an unsorted array

The same "sum too big, move right" rule now has no guarantee behind it. It can step past a valid pair that was sitting one cell away, and never look back.

Unsorted · target = 9 · the real pair gets skipped
Sum12Verdictmissed 3+6