Two Pointers Technique - Complete Guide

The two pointers technique is a powerful pattern for solving array and string problems efficiently. This guide covers all essential two pointer patterns and problems.

What is the Two Pointers Technique? #

Two pointers is a pattern where you use two references (pointers/indices) to traverse a data structure. This approach often reduces time complexity from O(n²) to O(n).

When to Use Two Pointers #

  • Sorted arrays - Search, remove duplicates
  • Pairs/triplets - Find pairs with target sum
  • Palindromes - Check if string is palindrome
  • Subarrays - Find subarray meeting condition
  • Merging - Merge sorted arrays

Common Patterns #

1. Opposite Ends Pattern #

Pointers start at beginning and end, move toward each other.

Two Sum II (Sorted Array):

function twoSum(numbers, target) {
  let left = 0;
  let right = numbers.length - 1;

  while (left < right) {
    const sum = numbers[left] + numbers[right];

    if (sum === target) {
      return [left + 1, right + 1];  // 1-indexed
    } else if (sum < target) {
      left++;
    } else {
      right--;
    }
  }

  return [];
}

console.log(twoSum([2, 7, 11, 15], 9));  // [1, 2]

Time: O(n), Space: O(1)

2. Same Direction Pattern #

Both pointers move in same direction at different speeds.

Remove Duplicates from Sorted Array:

function removeDuplicates(nums) {
  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;
}

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

Time: O(n), Space: O(1)

3. Sliding Window Pattern #

Two pointers define a window that slides through array.

Longest Substring Without Repeating Characters:

function lengthOfLongestSubstring(s) {
  const seen = new Set();
  let left = 0;
  let maxLen = 0;

  for (let right = 0; right < s.length; right++) {
    while (seen.has(s[right])) {
      seen.delete(s[left]);
      left++;
    }

    seen.add(s[right]);
    maxLen = Math.max(maxLen, right - left + 1);
  }

  return maxLen;
}

console.log(lengthOfLongestSubstring("abcabcbb"));  // 3 ("abc")

Time: O(n), Space: O(min(n, m))

Classic Two Pointer Problems #

Valid Palindrome #

function isPalindrome(s) {
  s = s.toLowerCase().replace(/[^a-z0-9]/g, '');

  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("A man, a plan, a canal: Panama"));  // true

Container With Most Water #

function maxArea(height) {
  let left = 0;
  let right = height.length - 1;
  let maxWater = 0;

  while (left < right) {
    const width = right - left;
    const minHeight = Math.min(height[left], height[right]);
    maxWater = Math.max(maxWater, width * minHeight);

    if (height[left] < height[right]) {
      left++;
    } else {
      right--;
    }
  }

  return maxWater;
}

console.log(maxArea([1, 8, 6, 2, 5, 4, 8, 3, 7]));  // 49

Three Sum #

function threeSum(nums) {
  nums.sort((a, b) => a - b);
  const result = [];

  for (let i = 0; i < nums.length - 2; i++) {
    if (i > 0 && nums[i] === nums[i - 1]) continue;

    let left = i + 1;
    let right = nums.length - 1;

    while (left < right) {
      const sum = nums[i] + nums[left] + nums[right];

      if (sum === 0) {
        result.push([nums[i], nums[left], nums[right]]);

        while (left < right && nums[left] === nums[left + 1]) left++;
        while (left < right && nums[right] === nums[right - 1]) right--;

        left++;
        right--;
      } else if (sum < 0) {
        left++;
      } else {
        right--;
      }
    }
  }

  return result;
}

console.log(threeSum([-1, 0, 1, 2, -1, -4]));
// [[-1, -1, 2], [-1, 0, 1]]

Move Zeroes #

function moveZeroes(nums) {
  let slow = 0;

  for (let fast = 0; fast < nums.length; fast++) {
    if (nums[fast] !== 0) {
      [nums[slow], nums[fast]] = [nums[fast], nums[slow]];
      slow++;
    }
  }

  return nums;
}

console.log(moveZeroes([0, 1, 0, 3, 12]));  // [1, 3, 12, 0, 0]

Remove Element #

function removeElement(nums, val) {
  let slow = 0;

  for (let fast = 0; fast < nums.length; fast++) {
    if (nums[fast] !== val) {
      nums[slow] = nums[fast];
      slow++;
    }
  }

  return slow;
}

const arr = [3, 2, 2, 3];
console.log(removeElement(arr, 3));  // 2
console.log(arr.slice(0, 2));  // [2, 2]

Sort Colors (Dutch National Flag) #

function sortColors(nums) {
  let low = 0;
  let mid = 0;
  let high = nums.length - 1;

  while (mid <= high) {
    if (nums[mid] === 0) {
      [nums[low], nums[mid]] = [nums[mid], nums[low]];
      low++;
      mid++;
    } else if (nums[mid] === 1) {
      mid++;
    } else {
      [nums[mid], nums[high]] = [nums[high], nums[mid]];
      high--;
    }
  }

  return nums;
}

console.log(sortColors([2, 0, 2, 1, 1, 0]));  // [0, 0, 1, 1, 2, 2]

Squares of Sorted Array #

function sortedSquares(nums) {
  const result = new Array(nums.length);
  let left = 0;
  let right = nums.length - 1;
  let pos = nums.length - 1;

  while (left <= right) {
    const leftSquare = nums[left] * nums[left];
    const rightSquare = nums[right] * nums[right];

    if (leftSquare > rightSquare) {
      result[pos] = leftSquare;
      left++;
    } else {
      result[pos] = rightSquare;
      right--;
    }

    pos--;
  }

  return result;
}

console.log(sortedSquares([-4, -1, 0, 3, 10]));
// [0, 1, 9, 16, 100]

Trapping Rain Water #

function trap(height) {
  let left = 0;
  let right = height.length - 1;
  let leftMax = 0;
  let rightMax = 0;
  let water = 0;

  while (left < right) {
    if (height[left] < height[right]) {
      if (height[left] >= leftMax) {
        leftMax = height[left];
      } else {
        water += leftMax - height[left];
      }
      left++;
    } else {
      if (height[right] >= rightMax) {
        rightMax = height[right];
      } else {
        water += rightMax - height[right];
      }
      right--;
    }
  }

  return water;
}

console.log(trap([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]));  // 6

Reverse String #

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

  while (left < right) {
    [s[left], s[right]] = [s[right], s[left]];
    left++;
    right--;
  }

  return s;
}

const arr = ['h', 'e', 'l', 'l', 'o'];
reverseString(arr);
console.log(arr);  // ['o', 'l', 'l', 'e', 'h']

Reverse Words in String #

function reverseWords(s) {
  const arr = s.split('');

  function reverse(left, right) {
    while (left < right) {
      [arr[left], arr[right]] = [arr[right], arr[left]];
      left++;
      right--;
    }
  }

  // Reverse entire string
  reverse(0, arr.length - 1);

  // Reverse each word
  let start = 0;
  for (let i = 0; i <= arr.length; i++) {
    if (i === arr.length || arr[i] === ' ') {
      reverse(start, i - 1);
      start = i + 1;
    }
  }

  return arr.join('');
}

console.log(reverseWords("the sky is blue"));
// "blue is sky the"

Partition Array #

function partition(nums, k) {
  let left = 0;
  let right = nums.length - 1;

  while (left <= right) {
    if (nums[left] < k) {
      left++;
    } else {
      [nums[left], nums[right]] = [nums[right], nums[left]];
      right--;
    }
  }

  return nums;
}

console.log(partition([3, 1, 2, 4, 5], 3));
// All elements < 3 before elements >= 3

Merge Sorted Arrays #

function merge(nums1, m, nums2, n) {
  let p1 = m - 1;
  let p2 = n - 1;
  let p = m + n - 1;

  while (p2 >= 0) {
    if (p1 >= 0 && nums1[p1] > nums2[p2]) {
      nums1[p] = nums1[p1];
      p1--;
    } else {
      nums1[p] = nums2[p2];
      p2--;
    }
    p--;
  }

  return nums1;
}

const nums1 = [1, 2, 3, 0, 0, 0];
const nums2 = [2, 5, 6];
merge(nums1, 3, nums2, 3);
console.log(nums1);  // [1, 2, 2, 3, 5, 6]

Intersection of Two Arrays #

function intersect(nums1, nums2) {
  nums1.sort((a, b) => a - b);
  nums2.sort((a, b) => a - b);

  const result = [];
  let i = 0;
  let j = 0;

  while (i < nums1.length && j < nums2.length) {
    if (nums1[i] === nums2[j]) {
      result.push(nums1[i]);
      i++;
      j++;
    } else if (nums1[i] < nums2[j]) {
      i++;
    } else {
      j++;
    }
  }

  return result;
}

console.log(intersect([1, 2, 2, 1], [2, 2]));  // [2, 2]

Find All Duplicates #

function findDuplicates(nums) {
  const result = [];
  let slow = 0;
  let fast = 0;

  // Detect cycle
  do {
    slow = nums[slow];
    fast = nums[nums[fast]];
  } while (slow !== fast);

  // Find cycle start
  slow = 0;
  while (slow !== fast) {
    slow = nums[slow];
    fast = nums[fast];
    result.push(slow);
  }

  return result;
}

Tips for Two Pointers #

  1. Sort if needed - Many problems require sorted input
  2. Think about direction - Opposite ends or same direction?
  3. Handle edge cases - Empty array, single element
  4. Watch bounds - Ensure pointers don’t go out of range
  5. Skip duplicates - When finding unique triplets/pairs
  6. Use while vs for - While for unknown iterations, for for known
  7. Update carefully - Increment/decrement in correct order

Time Complexity #

PatternTimeSpace
Opposite EndsO(n)O(1)
Same DirectionO(n)O(1)
Sliding WindowO(n)O(1) or O(k)
Three PointersO(n²)O(1)

When NOT to Use Two Pointers #

  • Unsorted data (unless you can sort it first)
  • Need to check all pairs (might need O(n²))
  • Complex dependencies between elements
  • Random access needed frequently

Two pointers is an elegant technique that simplifies many array and string problems. Master these patterns and you’ll solve problems more efficiently.