Two Pointers Technique: When to Use It and How to Spot It
The two pointers technique reduces O(n²) brute force solutions to O(n) by using two indices that move through an array or string in a coordinated way. It is one of the most frequently tested patterns in FAANG coding interviews.
When to Use Two Pointers
Use two pointers when:
- The input is sorted (or can be sorted without losing information)
- You need to find a pair or triplet satisfying a condition
- You need to compare elements from both ends of an array
- You need to remove duplicates in-place from a sorted array
- The problem involves a string or array and asks for a subarray/substring
The Three Variants
| Variant | Movement | Use Case |
|---|---|---|
| Opposite ends | Left and right move toward each other | Sorted array pairs, container problems |
| Same direction | Both move left to right | Removing duplicates, merging |
| Fast/slow | One moves 1 step, other moves 2 steps | Cycle detection, finding middle |
The Trigger Pattern
The problem says "sorted array" and asks for a pair → opposite ends. The problem says "remove duplicates in-place" → same direction. The problem says "detect a cycle" → fast/slow.
Opposite Ends: Two Sum II (Sorted Array)
This is the entry-level two pointers problem. Given a sorted array and a target, find two numbers that add up to the target.
def two_sum_sorted(numbers, target):
left, right = 0, len(numbers) - 1
while left < right:
current_sum = numbers[left] + numbers[right]
if current_sum == target:
return [left + 1, right + 1] # 1-indexed
elif current_sum < target:
left += 1 # Need a larger sum, move left pointer right
else:
right -= 1 # Need a smaller sum, move right pointer left
return []
Why this works: Since the array is sorted, moving the left pointer right increases the sum, and moving the right pointer left decreases the sum. You can eliminate one pointer's position in each step, making this O(n).
Time: O(n). Space: O(1).
Opposite Ends: Container With Most Water
This problem asks you to find two lines that together with the x-axis form a container holding the most water. It is a classic two pointers problem where you must prove the greedy approach works.
def max_area(height):
left, right = 0, len(height) - 1
max_water = 0
while left < right:
# Water is limited by the shorter line
width = right - left
water = min(height[left], height[right]) * width
max_water = max(max_water, water)
# Move the shorter line inward
if height[left] < height[right]:
left += 1
else:
right -= 1
return max_water
Why this works: Moving the taller line inward can never increase the area because the width decreases and the height is limited by the shorter line. Moving the shorter line gives a chance to find a taller line, potentially increasing the area.
Time: O(n). Space: O(1).
Opposite Ends: 3Sum
The 3Sum problem extends two pointers to triplets. Given an array, find all unique triplets that sum to zero. This is the most commonly asked two pointers variant at FAANG companies.
def three_sum(nums):
nums.sort()
result = []
for i in range(len(nums) - 2):
# Skip duplicate values for the first element
if i > 0 and nums[i] == nums[i - 1]:
continue
# Early termination: smallest sum too large
if nums[i] > 0:
break
left, right = i + 1, len(nums) - 1
while left < right:
total = nums[i] + nums[left] + nums[right]
if total == 0:
result.append([nums[i], nums[left], nums[right]])
# Skip duplicates for second element
while left < right and nums[left] == nums[left + 1]:
left += 1
# Skip duplicates for third element
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1
elif total < 0:
left += 1
else:
right -= 1
return result
Why this works: Sort the array first. Fix one element (nums[i]), then use two pointers on the remaining portion to find pairs that sum to -nums[i]. The duplicate skipping logic is critical — without it, you get duplicate triplets in the output.
Time: O(n²). Space: O(1) excluding output.
Same Direction: Remove Duplicates
Given a sorted array, remove duplicates in-place so each element appears only once. Return the new length. This uses the same-direction two pointers variant.
def remove_duplicates(nums):
if not nums:
return 0
slow = 0 # Position of last unique element
for fast in range(1, len(nums)):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
return slow + 1
Why this works: The slow pointer marks where the next unique element should go. The fast pointer scans ahead. When fast finds a new unique value, slow advances and copies it. This processes the array in one pass.
Time: O(n). Space: O(1).
Fast/Slow Pointers: Detect Cycle
Floyd's cycle detection algorithm uses two pointers moving at different speeds. If a cycle exists, the fast pointer eventually laps the slow pointer.
def has_cycle(head):
if not head or not head.next:
return False
slow = head
fast = head.next
while slow != fast:
if not fast or not fast.next:
return False
slow = slow.next
fast = fast.next.next
return True
Why this works: If there is no cycle, fast reaches the end (None). If there is a cycle, fast enters the cycle first and slow follows. Since fast gains one node per iteration, it will eventually catch slow from behind. The meeting point confirms the cycle.
Time: O(n). Space: O(1).
Common Mistakes
Not handling duplicates in 3Sum. Without the duplicate skipping logic, you return duplicate triplets. Always check
nums[i] == nums[i-1]before processing and skip matching values after finding a valid triplet.Moving the wrong pointer in container problem. Always move the shorter line. Moving the taller line can only decrease or maintain the area, never increase it.
Using two pointers on unsorted input (when not appropriate). Two pointers on opposite ends requires sorted input. If the problem says "unsorted," you need a different approach (hash map, etc.) or you must sort first (which changes indices).
Forgetting the fast pointer can overtake slow in cycle detection. Always check
fastandfast.nextfor None before advancing. A common bug is accessingfast.next.nextwhenfast.nextis None.Not initializing pointers correctly. For opposite-end problems, start with
left = 0andright = len(nums) - 1. For same-direction problems, start both at 0 or handle the first element separately.
Practice Problems
Start with these problems to master two pointers:
- Two Sum II - Input Array Is Sorted — The entry-level opposite-end problem. Master this before moving to harder variants.
- Container With Most Water — Tests your ability to prove why the greedy approach works. Frequently asked at Amazon.
- 3Sum — The most common two pointers variant in FAANG interviews. Focus on duplicate handling.
- Remove Duplicates from Sorted Array — The classic same-direction two pointers problem. Tests in-place array manipulation.
- Linked List Cycle — The entry-level fast/slow pointer problem. Foundational for cycle-related questions.
Practice What You Learned
Ready to put this into practice? Try a mock coding interview with an AI interviewer who can give you two pointers problems and evaluate your approach in real time.
Frequently Asked Questions
Can I use two pointers on an unsorted array?
Not for opposite-end problems. Opposite-end two pointers relies on sorted order to decide which pointer to move. For unsorted arrays, you need a hash map or must sort first. However, fast/slow pointers (cycle detection) work on unsorted linked lists because they do not depend on element order.
How do I prove the two pointers approach is correct?
For each pointer movement, explain why it cannot skip the optimal solution. In container with most water, moving the taller line cannot improve the area because the height is bounded by the shorter line. In 3Sum, sorting and using two pointers is correct because the sorted order lets you move pointers based on whether the sum is too large or too small.
What's the difference between two pointers and sliding window?
Two pointers typically refers to two specific indices with coordinated movement (converging or same-direction). Sliding window is a special case where both pointers define a window and you maintain state (sum, frequency count) as the window expands or contracts. Sliding window problems usually involve a contiguous subarray or substring.
How do I handle the case where no solution exists?
For opposite-end problems, the while loop naturally terminates when the pointers cross. Return an empty result at that point. For 3Sum, the outer loop ends when i reaches len(nums) - 2. There is no special case needed — if no triplets exist, the result list stays empty.