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 #
- Sort if needed - Many problems require sorted input
- Think about direction - Opposite ends or same direction?
- Handle edge cases - Empty array, single element
- Watch bounds - Ensure pointers don’t go out of range
- Skip duplicates - When finding unique triplets/pairs
- Use while vs for - While for unknown iterations, for for known
- Update carefully - Increment/decrement in correct order
Time Complexity #
| Pattern | Time | Space |
|---|---|---|
| Opposite Ends | O(n) | O(1) |
| Same Direction | O(n) | O(1) |
| Sliding Window | O(n) | O(1) or O(k) |
| Three Pointers | O(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.