Two pointers is one of the first patterns that starts paying rent in interviews. The reason it matters is not that two indexes are somehow special. It matters because the pattern helps you turn a brute-force nested scan into a single directional pass.
When the pattern is a good fit
Reach for two pointers when all of the following feel true:
- You are working on a linear structure like an array or string.
- The answer depends on comparing elements relative to each other.
- You can make progress by moving one boundary without losing previous work.
Common examples include:
- Finding a pair that matches a target sum
- Removing duplicates from a sorted array
- Checking whether a string is a palindrome
- Expanding or shrinking a candidate interval
Your first branch: opposite ends or same direction?
There are really two families here.
1. Opposite-end pointers
Use this when the structure is already sorted or when the order of outer characters matters.
left = 0
right = n - 1
while left < right:
compare values
move one or both pointers
This is the version you want for:
- Pair sum in a sorted array
- Container-style width decisions
- Palindrome checks
2. Same-direction pointers
Use this when one pointer represents the next write position or the start of a cleaned-up region.
write = 0
for read in range(n):
if should_keep(nums[read]):
nums[write] = nums[read]
write += 1
This is common in:
- Remove duplicates
- Move zeroes
- Partitioning tasks
A fast mental checklist
Before writing code, ask:
- Is the input sorted, or can I sort it without breaking the problem?
- What exact event causes
leftto move? - What exact event causes
rightto move? - Am I preserving an invariant after every move?
If you cannot answer the invariant question in one sentence, pause. That is usually where bugs hide.
Invariants keep pointer problems calm. They tell you what stays true even while indices move.
A canonical example
Suppose the array is sorted and you need a pair that sums to target.
def has_pair(nums, target):
left, right = 0, len(nums) - 1
while left < right:
current = nums[left] + nums[right]
if current == target:
return True
if current < target:
left += 1
else:
right -= 1
return False
Why this works:
- If the sum is too small, moving
rightinward would only keep the sum small or make it smaller. - If the sum is too large, moving
leftinward would only keep the sum large or make it larger.
That monotonic behavior is the reason the pattern works.
Mistakes that show up often
Moving both pointers without justification
People do this after a mismatch because it feels balanced. Usually it throws away possibilities.
Forgetting that sorting changes what you can return
If the problem asks for original indices, sorting means you now need to preserve index information.
Using the pattern where sliding window is actually the real idea
If the question is about a dynamic range with a condition on the whole interval, it may be a window problem rather than a raw pointer problem.
Practice progression
Use this order:
- Pair sum in sorted array
- Valid palindrome
- Remove duplicates from sorted array
- Squares of a sorted array
- Three sum after sorting
This sequence lets you feel how the invariant changes while the core intuition stays familiar.
Closing thought
Two pointers becomes reliable when you stop memorizing examples and start naming the movement rule. If you can say why each pointer moves, the implementation usually follows with much less friction.