Skip to main content
Data Structures & Algorithms8 min read

Two Pointers Pattern Explained

Master the two pointers technique for solving array and string problems efficiently. Learn when to use it and common problem patterns.

What is the Two Pointers Pattern?

The two pointers pattern is a technique where you use two pointers (usually indices) to iterate through a data structure, often an array or string. Instead of using nested loops which would give O(n²) time complexity, two pointers can solve many problems in O(n) time.

When to Use Two Pointers

Consider the two pointers pattern when:

  • Working with sorted arrays or strings - The sorted order lets you make decisions based on comparisons
  • Finding pairs or triplets that meet certain criteria
  • Checking for palindromes - Compare characters from both ends
  • Removing duplicates in-place from a sorted array
  • Reversing an array or string
  • Finding subarrays that meet a condition (often combined with sliding window)

Pattern Variations

1. Opposite Ends (Convergent Pointers)

Start with one pointer at the beginning and another at the end, moving them toward each other. Common for sorted arrays and palindromes.

function twoSum(numbers: number[], target: number): number[] {
  let left = 0
  let right = numbers.length - 1
  
  while (left < right) {
    const sum = numbers[left] + numbers[right]
    
    if (sum === target) {
      return [left, right]
    } else if (sum < target) {
      left++  // Need larger sum
    } else {
      right-- // Need smaller sum
    }
  }
  
  return [-1, -1]
}

// Example: twoSum([1, 2, 3, 4, 5], 7) → [1, 4]
// Because numbers[1] + numbers[4] = 2 + 5 = 7

2. Same Direction (Fast & Slow Pointers)

Both pointers start at the beginning but move at different speeds. Often used for in-place modifications or cycle detection.

function removeDuplicates(nums: number[]): number {
  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
}

// Example: removeDuplicates([1,1,2,2,3]) → 3
// Array becomes [1,2,3,2,3], return length 3

3. Distance-Based Pointers

Maintain a specific distance between pointers. Useful for problems like "find the nth element from the end."

function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {
  const dummy = new ListNode(0, head)
  let first = dummy
  let second = dummy
  
  // Move first pointer n+1 steps ahead
  for (let i = 0; i <= n; i++) {
    first = first.next!
  }
  
  // Move both until first reaches end
  while (first !== null) {
    first = first.next!
    second = second.next!
  }
  
  // Remove the nth node
  second.next = second.next!.next
  return dummy.next
}

Common Problems & Solutions

Problem 1: Valid Palindrome

Question: Given a string, determine if it's a palindrome, considering only alphanumeric characters and ignoring cases.

function isPalindrome(s: string): boolean {
  let left = 0
  let right = s.length - 1
  
  while (left < right) {
    // Skip non-alphanumeric from left
    while (left < right && !isAlphaNum(s[left])) {
      left++
    }
    
    // Skip non-alphanumeric from right
    while (left < right && !isAlphaNum(s[right])) {
      right--
    }
    
    // Compare characters (case-insensitive)
    if (s[left].toLowerCase() !== s[right].toLowerCase()) {
      return false
    }
    
    left++
    right--
  }
  
  return true
}

function isAlphaNum(c: string): boolean {
  return /[a-zA-Z0-9]/.test(c)
}

Problem 2: Container With Most Water

Question: Given an array of heights, find two lines that together with the x-axis form a container that holds the most water.

function maxArea(height: number[]): number {
  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 area = width * currentHeight
    
    maxWater = Math.max(maxWater, area)
    
    // Move pointer with smaller height
    if (height[left] < height[right]) {
      left++
    } else {
      right--
    }
  }
  
  return maxWater
}

// Key insight: Moving the smaller height gives us a chance
// to find a taller line that increases the area

Time & Space Complexity

Time Complexity: O(n) - We traverse the array once with both pointers
Space Complexity: O(1) - Only using pointer variables, no extra data structures

Pro Tips

  • Draw it out: Visualize the pointers moving through examples
  • Edge cases: Empty array, single element, all elements the same
  • Sorted requirement: Many two-pointer problems need sorted input
  • Decision criteria: Always know why you're moving each pointer
  • Loop condition: Usually left & lt; right for opposite ends

Practice Problems

Ready to test your understanding? Try these LeetCode problems:

  • Easy: Valid Palindrome, Remove Duplicates from Sorted Array
  • Medium: 3Sum, Container With Most Water, Longest Substring Without Repeating Characters
  • Hard: Trapping Rain Water, Minimum Window Substring

Practice with HireReady

Our platform includes 100+ two pointers problems with spaced repetition to ensure long-term retention. Start practicing now!

Start Free Practice →

Continue Learning