· 10 min read
Two Pointers LeetCode Pattern: Complete Guide with JavaScript Examples
Master the Two Pointers pattern with detailed explanations, JavaScript implementations, and solutions to common LeetCode problems. Learn when and how to apply this efficient O(n) technique.
The Two Pointers pattern is one of the most fundamental and versatile techniques in algorithmic problem-solving. It’s an elegant approach that can transform O(n²) brute force solutions into efficient O(n) algorithms, making it a favorite in coding interviews and competitive programming.
What is the Two Pointers Pattern?
The Two Pointers pattern involves using two pointers (indices) that traverse through a data structure, typically an array or string, in a coordinated manner. These pointers can:
- Move in opposite directions (from both ends toward the center)
- Move in the same direction at different speeds (fast and slow pointers)
- Move in the same direction with a fixed distance between them
When to Use Two Pointers
The Two Pointers pattern is particularly effective when:
- Working with sorted arrays or strings
- Looking for pairs, triplets, or subarrays with specific properties
- Need to optimize from O(n²) to O(n) time complexity
- Dealing with palindromes or symmetric structures
- Finding elements that satisfy certain conditions relative to each other
Pattern Variations
1. Opposite Direction (Convergent Pointers)
Pointers start at opposite ends and move toward each other.
function oppositeDirection(arr) {
let left = 0;
let right = arr.length - 1;
while (left < right) {
// Process current pair
// Move pointers based on condition
if (condition) {
left++;
} else {
right--;
}
}
}
2. Same Direction (Fast and Slow Pointers)
Both pointers move in the same direction at different speeds.
function sameDirection(arr) {
let slow = 0;
let fast = 0;
while (fast < arr.length) {
// Process based on condition
if (condition) {
slow++;
}
fast++;
}
}
3. Fixed Distance Pointers
Pointers maintain a fixed distance as they traverse.
function fixedDistance(arr, k) {
let left = 0;
let right = k;
while (right < arr.length) {
// Process window of size k
left++;
right++;
}
}
Detailed Problem Examples
Problem 1: Two Sum II (Sorted Array)
Problem Statement:
Given a 1-indexed array of integers numbers
that is already sorted in non-decreasing order, find two numbers such that they add up to a specific target
number.
Input: numbers = [2,7,11,15], target = 9
Output: [1,2]
Explanation: The sum of 2 and 7 is 9. Therefore, index1 = 1, index2 = 2.
Approach: Use opposite direction pointers. If the sum is too small, move the left pointer right to increase the sum. If too large, move the right pointer left to decrease the sum.
/**
* @param {number[]} numbers - sorted array of integers
* @param {number} target - target sum
* @return {number[]} - 1-indexed positions of the two numbers
*/
function twoSum(numbers, target) {
let left = 0;
let right = numbers.length - 1;
while (left < right) {
const currentSum = numbers[left] + numbers[right];
if (currentSum === target) {
return [left + 1, right + 1]; // Convert to 1-indexed
} else if (currentSum < target) {
left++; // Need a larger sum
} else {
right--; // Need a smaller sum
}
}
return []; // No solution found (shouldn't happen per problem constraints)
}
// Test cases
console.log(twoSum([2, 7, 11, 15], 9)); // [1,2]
console.log(twoSum([2, 3, 4], 6)); // [1,3]
console.log(twoSum([-1, 0], -1)); // [1,2]
Time Complexity: O(n) - Each element is visited at most once Space Complexity: O(1) - Only using two pointers
Problem 2: Valid Palindrome
Problem Statement: A phrase is a palindrome if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads the same forward and backward.
Input: s = "A man, a plan, a canal: Panama"
Output: true
Explanation: “amanaplanacanalpanama” is a palindrome.
Approach: Use opposite direction pointers, comparing characters from both ends while skipping non-alphanumeric characters.
/**
* @param {string} s - input string
* @return {boolean} - true if palindrome, false otherwise
*/
function isPalindrome(s) {
let left = 0;
let right = s.length - 1;
while (left < right) {
// Skip non-alphanumeric characters from left
while (left < right && !isAlphanumeric(s[left])) {
left++;
}
// Skip non-alphanumeric characters from right
while (left < right && !isAlphanumeric(s[right])) {
right--;
}
// Compare characters (case-insensitive)
if (s[left].toLowerCase() !== s[right].toLowerCase()) {
return false;
}
left++;
right--;
}
return true;
}
/**
* Helper function to check if character is alphanumeric
* @param {string} char - single character
* @return {boolean} - true if alphanumeric
*/
function isAlphanumeric(char) {
return /[a-zA-Z0-9]/.test(char);
}
// Test cases
console.log(isPalindrome('A man, a plan, a canal: Panama')); // true
console.log(isPalindrome('race a car')); // false
console.log(isPalindrome(' ')); // true
Time Complexity: O(n) - Single pass through the string Space Complexity: O(1) - Only using two pointers
Problem 3: Container With Most Water
Problem Statement:
You are given an integer array height
of length n
. There are n
vertical lines drawn such that the two endpoints of the i
th line are (i, 0)
and (i, height[i])
. Find two lines that together with the x-axis form a container that can hold the most water.
Input: height = [1,8,6,2,5,4,8,3,7]
Output: 49
Explanation: The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. The max area of water the container can contain is 49.
Approach: Use opposite direction pointers. Always move the pointer with the smaller height because moving the taller one will only decrease the area.
/**
* @param {number[]} height - array of heights
* @return {number} - maximum area of water
*/
function maxArea(height) {
let left = 0;
let right = height.length - 1;
let maxWater = 0;
while (left < right) {
// Calculate current area
const width = right - left;
const currentHeight = Math.min(height[left], height[right]);
const currentArea = width * currentHeight;
// Update maximum
maxWater = Math.max(maxWater, currentArea);
// Move the pointer with smaller height
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return maxWater;
}
// Test cases
console.log(maxArea([1, 8, 6, 2, 5, 4, 8, 3, 7])); // 49
console.log(maxArea([1, 1])); // 1
console.log(maxArea([1, 2, 1])); // 2
Time Complexity: O(n) - Single pass through the array Space Complexity: O(1) - Only using two pointers
Problem 4: Remove Duplicates from Sorted Array
Problem Statement:
Given an integer array nums
sorted in non-decreasing order, remove the duplicates in-place such that each unique element appears only once. Return the number of unique elements.
Input: nums = [1,1,2]
Output: 2, nums = [1,2,_]
Explanation: Your function should return k = 2, with the first two elements of nums being 1 and 2 respectively.
Approach: Use same direction pointers where slow pointer tracks the position for unique elements and fast pointer explores the array.
/**
* @param {number[]} nums - sorted array with possible duplicates
* @return {number} - number of unique elements
*/
function removeDuplicates(nums) {
if (nums.length <= 1) return nums.length;
let slow = 0; // Position for next unique element
let fast = 1; // Explorer pointer
while (fast < nums.length) {
// If we find a new unique element
if (nums[fast] !== nums[slow]) {
slow++; // Move to next position for unique element
nums[slow] = nums[fast]; // Place the new unique element
}
fast++;
}
return slow + 1; // Number of unique elements
}
// Test cases
let nums1 = [1, 1, 2];
console.log(removeDuplicates(nums1)); // 2
console.log(nums1.slice(0, 2)); // [1,2]
let nums2 = [0, 1, 1, 2, 2, 3, 3, 3, 4];
console.log(removeDuplicates(nums2)); // 5
console.log(nums2.slice(0, 5)); // [0,1,2,3,4]
Key Insight: We increment slow++
before assigning because slow
represents the position where the next unique element should be placed. When we find a new unique element, we first move to the next available position, then place the element there.
Time Complexity: O(n) - Single pass through the array Space Complexity: O(1) - In-place modification
Problem 5: 3Sum
Problem Statement:
Given an integer array nums
, return all the triplets [nums[i], nums[j], nums[k]]
such that i != j
, i != k
, and j != k
, and nums[i] + nums[j] + nums[k] == 0
.
Input: nums = [-1,0,1,2,-1,-4]
Output: [[-1,-1,2],[-1,0,1]]
Explanation: The distinct triplets are [-1,-1,2] and [-1,0,1].
Approach: Sort the array, then for each element, use two pointers to find pairs that sum to the negative of that element.
/**
* @param {number[]} nums - array of integers
* @return {number[][]} - array of unique triplets that sum to zero
*/
function threeSum(nums) {
const result = [];
nums.sort((a, b) => a - b); // Sort the array
for (let i = 0; i < nums.length - 2; i++) {
// Skip duplicate values for first element
if (i > 0 && nums[i] === nums[i - 1]) continue;
let left = i + 1;
let right = nums.length - 1;
const target = -nums[i]; // We want nums[left] + nums[right] = target
while (left < right) {
const currentSum = nums[left] + nums[right];
if (currentSum === target) {
result.push([nums[i], nums[left], nums[right]]);
// Skip duplicates for second element
while (left < right && nums[left] === nums[left + 1]) {
left++;
}
// Skip duplicates for third element
while (left < right && nums[right] === nums[right - 1]) {
right--;
}
left++;
right--;
} else if (currentSum < target) {
left++;
} else {
right--;
}
}
}
return result;
}
// Test cases
console.log(threeSum([-1, 0, 1, 2, -1, -4])); // [[-1,-1,2],[-1,0,1]]
console.log(threeSum([0, 1, 1])); // []
console.log(threeSum([0, 0, 0])); // [[0,0,0]]
Time Complexity: O(n²) - O(n log n) for sorting + O(n²) for the nested loops Space Complexity: O(1) - Excluding the output array
Common Mistakes and Tips
Mistakes to Avoid
- Off-by-one errors: Be careful with array bounds and loop conditions
- Infinite loops: Ensure pointers always move toward termination
- Missing edge cases: Consider empty arrays, single elements, or all same elements
- Wrong pointer movement: Make sure you’re moving the correct pointer based on the condition
Pro Tips
- Visualize the problem: Draw the array and pointer movements
- Handle duplicates carefully: Especially in problems like 3Sum
- Consider all edge cases: Empty arrays, single elements, no solution
- Optimize space when possible: Use the input array for in-place modifications
- Test with simple examples first: Verify your logic with small inputs
Pattern Recognition Checklist
Use Two Pointers when you see:
- ✅ Sorted array or string
- ✅ Looking for pairs/triplets with specific sum
- ✅ Palindrome-related problems
- ✅ Removing duplicates in-place
- ✅ Finding subarrays with certain properties
- ✅ Merging sorted arrays
- ✅ Fast and slow pointer scenarios (cycle detection)
Time and Space Complexity Analysis
Problem Type | Time Complexity | Space Complexity | Notes |
---|---|---|---|
Two Sum (sorted) | O(n) | O(1) | Opposite direction |
Palindrome | O(n) | O(1) | Opposite direction |
Remove duplicates | O(n) | O(1) | Same direction |
3Sum | O(n²) | O(1) | Nested two pointers |
Container with water | O(n) | O(1) | Opposite direction |
Practice Problems
Easy Level
- Two Sum II - Basic opposite direction pointers
- Valid Palindrome - String manipulation with two pointers
- Remove Duplicates from Sorted Array - Same direction pointers
- Merge Sorted Array - Two arrays with two pointers each
Medium Level
- 3Sum - Combination of sorting and two pointers
- Container With Most Water - Greedy approach with two pointers
- Sort Colors - Dutch National Flag problem
Conclusion
The Two Pointers pattern is a powerful technique that can dramatically improve the efficiency of your solutions. By mastering this pattern, you’ll be able to:
- Transform O(n²) brute force solutions into O(n) algorithms
- Solve complex array and string problems elegantly
- Handle in-place modifications efficiently
- Recognize when and how to apply this pattern in interviews
Remember to practice regularly, understand the underlying principles, and always consider edge cases. The key to mastering Two Pointers is recognizing the problem patterns and choosing the right pointer movement strategy.
Start with the easier problems to build confidence, then gradually work your way up to more complex variations. With consistent practice, this pattern will become second nature, and you’ll find yourself reaching for it naturally when faced with appropriate problems in coding interviews.