backend / dsa / arrays strings / 01_two_pointers.md

Two pointers

4 interview angles 3 min read source

Two pointers

Use two indices (or pointers) that move through the same array, often from opposite ends or at different speeds. Common when the input is sorted or when you’re looking for pairs/subarrays.

When to apply

  • Input is sorted, or you sort it yourself
  • Finding pairs that sum to a target
  • Reversing / partitioning in place
  • Removing duplicates from a sorted array
  • Squeezing from both ends

Pattern: opposite ends

def two_sum_sorted(arr: list[int], target: int) -> tuple[int, int] | None:
    """In a sorted array, find indices of two values summing to target."""
    left, right = 0, len(arr) - 1
    while left < right:
        s = arr[left] + arr[right]
        if s == target:
            return (left, right)
        elif s < target:
            left += 1
        else:
            right -= 1
    return None

O(n) time, O(1) space. The sortedness is what makes the directional move correct: if the sum is too small, only moving left rightward can increase it.

Pattern: same direction (slow + fast)

def remove_duplicates(arr: list[int]) -> int:
    """In-place dedupe of a sorted array. Returns new length."""
    if not arr:
        return 0
    slow = 0
    for fast in range(1, len(arr)):
        if arr[fast] != arr[slow]:
            slow += 1
            arr[slow] = arr[fast]
    return slow + 1

slow tracks the next position to write; fast scans for new values.

Pattern: meet in the middle (palindrome)

def is_palindrome(s: str) -> bool:
    s = "".join(c.lower() for c in s if c.isalnum())
    left, right = 0, len(s) - 1
    while left < right:
        if s[left] != s[right]:
            return False
        left += 1
        right -= 1
    return True

Problem: container with most water

Given height[], find two lines that form a container with most water:

def max_area(height: list[int]) -> int:
    left, right = 0, len(height) - 1
    best = 0
    while left < right:
        h = min(height[left], height[right])
        best = max(best, h * (right - left))
        # Move the shorter wall — moving the taller one can never improve
        if height[left] < height[right]:
            left += 1
        else:
            right -= 1
    return best

Greedy insight: width shrinks each step, so we must increase height — only achievable by moving the shorter side.

Problem: 3-sum

Find all unique triplets that sum to zero:

def three_sum(nums: list[int]) -> list[list[int]]:
    nums.sort()
    result = []
    for i in range(len(nums) - 2):
        if i > 0 and nums[i] == nums[i - 1]:
            continue   # skip duplicates for first element
        target = -nums[i]
        left, right = i + 1, len(nums) - 1
        while left < right:
            s = nums[left] + nums[right]
            if s == target:
                result.append([nums[i], nums[left], nums[right]])
                left += 1
                right -= 1
                while left < right and nums[left] == nums[left - 1]:
                    left += 1
                while left < right and nums[right] == nums[right + 1]:
                    right -= 1
            elif s < target:
                left += 1
            else:
                right -= 1
    return result

O(n²) — sort + n × two-pointer.

Pitfalls

  • Off-by-one on while left < right vs <= — for pairs you almost always want strict <.
  • Forgetting to handle duplicates in result-collecting problems (3-sum, 4-sum).
  • Sorting destroys original indices — keep them if needed.

Interview angle

  • “When do you reach for two pointers?” - a sorted array where you need a pair or triple meeting a condition, in-place partitioning or removal, or comparing from both ends. The signal is that moving one pointer lets you rule out a whole range without checking it.
  • “Why is it O(n) rather than O(n^2)?” - each pointer only moves forward, so together they take at most 2n steps. That monotonic movement is what replaces the nested loop.
  • “Two-sum on a sorted array?” - pointers at both ends; if the sum is too small move left up, too large move right down. O(n) time and O(1) space, versus O(n) extra space for the hash-map approach on an unsorted array.
  • “Fast and slow pointers?” - cycle detection, finding the middle, and the k-th from the end in one pass over a linked list. See ../03_linked_lists/01_basics_reverse_cycle.md.