Two-Pointer Pattern

Eliminate Nested Loops by Moving Two Indices Toward Each Other

Two-Pointer Pattern

The two-pointer technique collapses O(n²) brute-force solutions into O(n) by keeping two indices that advance based on a comparison, never revisiting the same pair.

5 min read Level 2/5 #dsa#arrays#two-pointers
What you'll learn
  • Apply opposing pointers to solve two-sum on a sorted array and palindrome detection
  • Apply same-direction pointers to remove duplicates and partition arrays
  • Explain why two pointers achieve O(n) time and O(1) extra space

The two-pointer pattern uses two index variables that move through an array in a coordinated way. Because each pointer advances monotonically — never going backwards — the total number of steps across both pointers is at most 2n, giving an O(n) algorithm even when the naive approach is O(n²).

Opposing Pointers

Place one pointer at the start and one at the end. Advance or retreat based on a comparison at each step. The canonical problems are:

Two-Sum on a Sorted Array

Given a sorted array and a target, find two indices whose values sum to target.

function twoSum(nums, target) {
  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++;   // need a larger value
    else right--;               // need a smaller value
  }
  return null; // no pair found
}

// Walk-through: nums = [1, 3, 4, 6, 9], target = 10
// left=0(1), right=4(9) → sum=10 → return [0,4]
console.log(twoSum([1, 3, 4, 6, 9], 10)); // [0, 4]

Palindrome Check

function isPalindrome(s) {
  let left = 0;
  let right = s.length - 1;

  while (left < right) {
    if (s[left] !== s[right]) return false;
    left++;
    right--;
  }
  return true;
}

console.log(isPalindrome("racecar")); // true
console.log(isPalindrome("hello"));   // false

Same-Direction Pointers

Both pointers start at the left but move at different speeds. The “slow” pointer tracks where the next valid element should be written; the “fast” pointer scans ahead.

Remove Duplicates In-Place

function removeDuplicates(nums) {
  // nums must be sorted
  if (nums.length === 0) return 0;

  let slow = 0;

  for (let fast = 1; fast < nums.length; fast++) {
    if (nums[fast] !== nums[slow]) {
      slow++;
      nums[slow] = nums[fast];
    }
  }
  return slow + 1; // length of deduplicated prefix
}

const arr = [1, 1, 2, 3, 3, 4];
const len = removeDuplicates(arr);
console.log(arr.slice(0, len)); // [1, 2, 3, 4]

Complexity

VariantTimeSpaceNotes
Opposing pointers (two-sum)O(n)O(1)Requires sorted input
Opposing pointers (palindrome)O(n)O(1)Works on any sequence
Same-direction (remove dups)O(n)O(1)In-place, sorted input
Brute-force nested loopsO(n²)O(1)Baseline comparison

The pattern always requires sorted data or a property (like symmetry) that makes it safe to discard elements when a pointer advances.

Up Next

When the “window” between two pointers has a variable width and you track a running aggregate, you have a sliding window — the next natural extension.

Sliding Window Pattern →