A lot of array problems start with an obvious nested loop: for every element, look at every other element. That is O(n²), and on 100,000 items it means ten billion comparisons. The two pointer technique often solves the same problem in a single O(n) pass using two indexes and a clear rule for moving them.

The rule matters more than the code. Every time you move a pointer, you are ruling out a set of candidates for good. If you can explain why those candidates can never be the answer, the algorithm is correct.

Three shapes of two pointers

Shape Pointers start Typical problems
Opposite ends left = 0, right = n - 1 Pair sum in sorted array, container with most water, palindrome check
Same direction (fast and slow) both at 0 Remove duplicates, move zeroes, partition in place
Two sequences one pointer per array Merge sorted arrays, subsequence check, intersection

Recognizing the shape is usually the hardest step. The code for each is short.

Shape 1: opposite ends

Find two numbers in a sorted array that add up to a target.

function pairWithSum(nums: number[], target: number): [number, number] | null {
  let left = 0;
  let right = nums.length - 1;
  while (left < right) {
    const sum = nums[left] + nums[right];
    if (sum === target) return [left, right];
    if (sum < target) left++;    // nums[left] is too small to pair with anything left
    else right--;                // nums[right] is too large to pair with anything left
  }
  return null;
}

Why it is safe to skip

If nums[left] + nums[right] is too small, then nums[left] plus any remaining element is also too small, because nums[right] is the largest one left. So nums[left] can never be part of the answer and we drop it. The same argument, mirrored, drops nums[right] when the sum is too large.

nums = [1, 3, 4, 6, 8, 11]   target = 10

L=1  R=11  sum=12  too big   -> R moves left
L=1  R=8   sum=9   too small -> L moves right
L=3  R=8   sum=11  too big   -> R moves left
L=3  R=6   sum=9   too small -> L moves right
L=4  R=6   sum=10  found

Five steps instead of fifteen pair checks, and the gap only grows with input size.

Shape 2: fast and slow

Remove duplicates from a sorted array in place and return the new length.

function removeDuplicates(nums: number[]): number {
  if (nums.length === 0) return 0;
  let write = 1;                          // next position for a unique value
  for (let read = 1; read < nums.length; read++) {
    if (nums[read] !== nums[write - 1]) {
      nums[write] = nums[read];
      write++;
    }
  }
  return write;
}

The read pointer explores every element. The write pointer only advances when it has something worth keeping. Everything before write is the finished answer, which is the invariant that makes the code easy to trust.

The same pattern solves "move all zeroes to the end", "remove element", and the partition step of quicksort.

Shape 3: two sequences

Check whether s is a subsequence of t (the characters of s appear in t in order).

function isSubsequence(s: string, t: string): boolean {
  let i = 0;
  for (let j = 0; j < t.length && i < s.length; j++) {
    if (s[i] === t[j]) i++;
  }
  return i === s.length;
}

Each pointer walks its own sequence once, so the whole check is O(|s| + |t|). Merging two sorted lists follows the same idea: compare the heads, take the smaller one, and advance only that pointer.

Two pointers vs sliding window

A sliding window is a special case of same-direction pointers where you care about everything between them, such as "longest substring without repeating characters". Plain two pointers usually care only about the elements at the pointers.

Question to ask Likely technique
Is the input sorted, and do I need a pair or triplet? Opposite-end pointers
Do I need to rewrite the array in place? Fast and slow pointers
Do I need the best contiguous range? Sliding window
Am I walking two sorted inputs together? One pointer per sequence

How to spot it in an interview

  • The input is sorted, or sorting it first costs only O(n log n).
  • You are looking for a pair, a triplet, or a partition.
  • The problem says "in place" or "O(1) extra space".
  • A brute-force solution compares every pair.

When you explain your solution, say out loud why each move is safe: "the current sum is too small, and this is already the largest remaining value, so the left element can never be used." That one sentence is what interviewers listen for.

Want more practice? Read the full two pointers guide.