Two Pointers Pattern: A Visual Guide to Solving Array Problems
Learn how the two pointers technique works, when to use it, and walk through classic problems like Two Sum II and Container With Most Water.
Two Pointers Pattern: A Visual Guide to Solving Array Problems
The two pointers technique is one of the most versatile patterns in coding interviews. Once you see it, you'll recognise it everywhere — from simple sorted-array searches to tricky substring problems.
When to Reach for Two Pointers
Two pointers works when you can reduce a brute-force O(n²) scan to O(n) by maintaining two indices that move toward each other (or in the same direction) based on some condition.
Look for these signals:
- The input is sorted (or can be sorted without breaking the answer).
- You need to find a pair or subarray satisfying a constraint.
- A brute-force nested loop compares every pair.
Classic Example: Two Sum II (Sorted Input)
Given a 1-indexed sorted array numbers and a target, find two numbers that add up to target.
def two_sum(numbers: list[int], target: int) -> list[int]:
left, right = 0, len(numbers) - 1
while left < right:
current = numbers[left] + numbers[right]
if current == target:
return [left + 1, right + 1]
elif current < target:
left += 1
else:
right -= 1
return []Why it works: Because the array is sorted, moving left right increases the sum, and moving right left decreases it. Each step eliminates at least one candidate, giving us O(n) time.
Container With Most Water
Given heights height[0..n-1], find two lines that form a container holding the most water.
def max_area(height: list[int]) -> int:
left, right = 0, len(height) - 1
best = 0
while left < right:
w = right - left
h = min(height[left], height[right])
best = max(best, w * h)
if height[left] < height[right]:
left += 1
else:
right -= 1
return bestThe shorter line is always the bottleneck — moving it inward is the only way to possibly find a taller line, even though the width shrinks.
Same-Direction Variant: Remove Duplicates
Two pointers don't always move toward each other. In the "slow/fast" variant both move left-to-right:
def remove_duplicates(nums: list[int]) -> int:
if not nums:
return 0
slow = 0
for fast in range(1, len(nums)):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
return slow + 1slow marks the last unique element; fast scans ahead for the next new value.
Complexity
| Variant | Time | Space |
|---|---|---|
| Opposite ends | O(n) | O(1) |
| Slow / fast | O(n) | O(1) |
| With sorting first | O(n log n) | O(1)* |
* Ignoring sort's internal space.
Practice These Next
- 3Sum — sort + fix one element + two-pointer inner loop.
- Trapping Rain Water — opposite-end pointers tracking max heights.
- Linked List Cycle — slow/fast pointers (Floyd's algorithm).
Want to trace through these step-by-step with variable visualisation? Try the interactive walkthrough on Codebrew.