What Problem Does It Solve?
Imagine squeezing a tube of toothpaste from both ends toward the middle. You are using information from two positions simultaneously to converge on an answer. Two Pointers applies this same idea to arrays and strings.
Use Two Pointers when the data is sorted (or can be treated symmetrically) and you need to find a pair, triplet, or verify a property like palindrome. The key insight is that sorting gives you a guarantee: moving the left pointer right increases the value, moving the right pointer left decreases it. You can use this to navigate intelligently instead of checking every combination.
The Classic Example: Two Sum on a Sorted Array
Given sorted array [1, 2, 4, 6, 8, 10], find two numbers that sum to 12.
Brute force: Check every pair → $O(N^2)$.
Two Pointers:
1. Place left at index 0 (value 1), right at the last index (value 10).
2. Compute sum = arr[left] + arr[right] = 1 + 10 = 11. Too small → move left right.
3. sum = 2 + 10 = 12. Found it.
The decision rule is deterministic: if the sum is too large, the right pointer must move left (to decrease the larger value); if too small, the left pointer must move right. This eliminates the need for a nested loop entirely.
def two_sum_sorted(arr, target):
left, right = 0, len(arr) - 1
while left < right:
current_sum = arr[left] + arr[right]
if current_sum == target:
return [left, right]
elif current_sum < target:
left += 1 # Need a bigger sum
else:
right -= 1 # Need a smaller sum
return [] # No pair found
print(two_sum_sorted([1, 2, 4, 6, 8, 10], 12)) # Output: [1, 5] (values 2 + 10)
Other Two-Pointer Shapes
Not all Two Pointer problems use the "opposite ends" configuration. Some use same-direction pointers (a slow pointer and a fast pointer), which is the basis for:
- Removing duplicates in-place: slow pointer tracks the write position, fast pointer scans ahead.
- Detecting cycles in a linked list (Floyd's algorithm): slow moves one step, fast moves two. If they meet, there is a cycle.