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
leftandrightstart 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.
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.
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.
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.
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.
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.
One side steps in at a time. Never both, never back out.
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.
fast always advances. slow only catches up when it has to.
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.
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.