When you hear the words “optimize an O(n²) solution” — think of two pointers. It’s one of the most powerful patterns in coding interviews and real-world problem solving.
This guide will walk you through the core principles of the two-pointer technique, real LeetCode-style problems, detailed code walkthroughs, and tips to recognize when this strategy applies.
What is the Two-Pointer Technique?
The two-pointer technique involves using two indices to scan through an array or string — usually from opposite ends or at different speeds — to solve problems more efficiently than brute force.
Common Use Cases:
- Searching in a sorted array
- Finding pairs, triplets, or ranges
- Optimizing based on distance or height
- Checking symmetry (e.g., palindromes)
- Avoiding extra space used in prefix arrays
Problem Walkthroughs Using Two Pointers
1. Two Sum (Sorted Array)
Problem:
Find two indices in a sorted array such that their values add up to a given target.
Why Two Pointers?
The array is sorted — so we can narrow down from both ends instead of scanning everything.
Solution:
function twoSum(numbers: number[], target: number): number[] {
let left = 0, right = numbers.length - 1;
while (left < right) {
const sum = numbers[left] + numbers[right];
if (sum === target) return [left, right];
else if (sum < target) left++;
else right--;
}
return [-1, -1];
}
Time: O(n)
Space: O(1)
2. Three Sum
Problem:
Find all unique triplets in an array that sum to zero.
Why Two Pointers?
We sort the array, fix one number, and use two pointers to find the remaining pair. This avoids brute force (O(n³)).
Solution:
function threeSum(nums: number[]): number[][] {
nums.sort((a, b) => a - b);
const result: number[][] = [];
for (let i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] === nums[i - 1]) continue;
let left = i + 1, 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 (nums[left] === nums[left + 1]) left++;
while (nums[right] === nums[right - 1]) right--;
left++;
right--;
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return result;
}
Time: O(n²)
Space: O(1)
3. Container With Most Water
Problem:
Find the two lines that form the container holding the most water.
Why Two Pointers?
We’re comparing two vertical lines and maximizing area based on distance and height. Move the pointer at the shorter line inward.
Solution:
function maxArea(height: number[]): number {
let left = 0, right = height.length - 1;
let maxWater = 0;
while (left < right) {
const area = Math.min(height[left], height[right]) * (right - left);
maxWater = Math.max(maxWater, area);
if (height[left] < height[right]) left++;
else right--;
}
return maxWater;
}
Time: O(n)
Space: O(1)
4. Valid Palindrome (Bonus: Two-Pointer + Filtering)
Problem:
Check if a string is a palindrome after removing non-alphanumeric characters.
Why Two Pointers?
We compare from both ends toward the middle, skipping unwanted characters.
Solution:
function isPalindrome(s: string): boolean {
s = s.toLowerCase();
let left = 0, right = s.length - 1;
while (left < right) {
while (left < right && !isAlphaNumeric(s[left])) left++;
while (left < right && !isAlphaNumeric(s[right])) right--;
if (s[left] !== s[right]) return false;
left++;
right--;
}
return true;
}
function isAlphaNumeric(c: string): boolean {
return /^[a-z0-9]$/i.test(c);
}
Time: O(n)
Space: O(1)
5. Trapping Rain Water
Problem:
Given elevation heights, calculate how much water can be trapped between bars after rain.
Why Two Pointers?
We avoid using extra space for leftMax[]
and rightMax[]
arrays by maintaining running maxes while scanning from both sides.
Solution:
function trap(height: number[]): number {
let left = 0, right = height.length - 1;
let leftMax = 0, rightMax = 0, 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;
}
Time: O(n)
Space: O(1)
Summary Table
Problem | Technique | Time | Space |
---|---|---|---|
Two Sum (sorted) | Two Pointers | O(n) | O(1) |
Three Sum | Sort + Two Pointers | O(n²) | O(1) |
Container With Most Water | Two Pointers + Math | O(n) | O(1) |
Valid Palindrome | Two Pointers + Filter | O(n) | O(1) |
Trapping Rain Water | Two Pointers + Max Track | O(n) | O(1) |
How to Recognize Two-Pointer Problems
Clue | Use Two Pointers? |
---|---|
The array is sorted | ✅ Definitely |
You’re checking values from both ends | ✅ Yes |
You’re solving a palindrome, distance, or water problem | ✅ |
Brute-force is O(n²) and you want to optimize |
✅ Try it! |
✨ Final Thoughts
The Two Pointer pattern is not just a trick — it’s a mindset. When used right, it transforms inefficient code into elegant, optimal solutions.
Whether you’re solving a leetcode problem or optimizing code in production, understanding this technique gives you a serious edge.