· 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:

  1. Working with sorted arrays or strings
  2. Looking for pairs, triplets, or subarrays with specific properties
  3. Need to optimize from O(n²) to O(n) time complexity
  4. Dealing with palindromes or symmetric structures
  5. 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 ith 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

  1. Off-by-one errors: Be careful with array bounds and loop conditions
  2. Infinite loops: Ensure pointers always move toward termination
  3. Missing edge cases: Consider empty arrays, single elements, or all same elements
  4. Wrong pointer movement: Make sure you’re moving the correct pointer based on the condition

Pro Tips

  1. Visualize the problem: Draw the array and pointer movements
  2. Handle duplicates carefully: Especially in problems like 3Sum
  3. Consider all edge cases: Empty arrays, single elements, no solution
  4. Optimize space when possible: Use the input array for in-place modifications
  5. 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 TypeTime ComplexitySpace ComplexityNotes
Two Sum (sorted)O(n)O(1)Opposite direction
PalindromeO(n)O(1)Opposite direction
Remove duplicatesO(n)O(1)Same direction
3SumO(n²)O(1)Nested two pointers
Container with waterO(n)O(1)Opposite direction

Practice Problems

Easy Level

  1. Two Sum II - Basic opposite direction pointers
  2. Valid Palindrome - String manipulation with two pointers
  3. Remove Duplicates from Sorted Array - Same direction pointers
  4. Merge Sorted Array - Two arrays with two pointers each

Medium Level

  1. 3Sum - Combination of sorting and two pointers
  2. Container With Most Water - Greedy approach with two pointers
  3. 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.

Back to Blog