← Index

Techniques & Patterns

Two Pointers

Walk two positions through data instead of nesting loops.

What it is

Instead of comparing every pair with a nested loop (O(n²)), keep two pointers moving through the structure — often from opposite ends toward the middle, or one fast/one slow — and let their relationship do the work. It turns a lot of 'check every combination' problems into a single O(n) pass.

Real-world examples

Where you've probably used this already

Complexity

Time O(n) — single pass, two pointers
Space O(1) — no extra structure

Code

// Two Pointers — walk two positions through a structure (often from
// both ends, or one fast/one slow) instead of nesting loops.
// Turns a lot of O(n^2) "check every pair" problems into O(n).

// --- Example 1: pair sum in a SORTED array ---
function pairSumSorted(sorted, target) {
  let left = 0;
  let right = sorted.length - 1;

  while (left < right) {
    const sum = sorted[left] + sorted[right];
    if (sum === target) return [sorted[left], sorted[right]];
    if (sum < target) left++;   // need a bigger sum, move left pointer up
    else right--;                // need a smaller sum, move right pointer down
  }
  return null;
}

// --- Example 2: reverse an array in place ---
function reverseInPlace(arr) {
  let left = 0;
  let right = arr.length - 1;
  while (left < right) {
    [arr[left], arr[right]] = [arr[right], arr[left]];
    left++;
    right--;
  }
  return arr;
}

// --- Example 3: is this string a palindrome? ---
function isPalindrome(str) {
  let left = 0;
  let right = str.length - 1;
  while (left < right) {
    if (str[left] !== str[right]) return false;
    left++;
    right--;
  }
  return true;
}

// --- Example 4: remove duplicates from a sorted array in place (fast/slow) ---
function removeDuplicates(sorted) {
  if (sorted.length === 0) return 0;
  let slow = 0; // last known-unique position
  for (let fast = 1; fast < sorted.length; fast++) {
    if (sorted[fast] !== sorted[slow]) {
      slow++;
      sorted[slow] = sorted[fast];
    }
  }
  return slow + 1; // new length
}

// --- Example Usage ---
console.log("pairSumSorted([1,3,4,7,9], 11):", pairSumSorted([1, 3, 4, 7, 9], 11)); // [4,7]
console.log("reverseInPlace([1,2,3,4]):", reverseInPlace([1, 2, 3, 4])); // [4,3,2,1]
console.log("isPalindrome('racecar'):", isPalindrome("racecar")); // true
const dups = [1, 1, 2, 2, 3];
console.log("removeDuplicates length:", removeDuplicates(dups), dups.slice(0, 3)); // 3 [1,2,3]

// You reach for this pattern any time you're comparing/merging from
// both ends: form validation (matching brackets/quotes), diffing two
// sorted lists, or trimming whitespace from both ends of a string manually.