The Two Pointer technique is one of those rare algorithmic tools that feels like cheating once you truly understand it. Problems that look like they need nested loops — O(n²) brute force — suddenly collapse into a single O(n) pass. This article covers everything: the intuition, the patterns, step-by-step walkthroughs, and a map of when to actually use it.
What Is the Two Pointer Technique?
At its core, the two pointer approach uses two index variables (pointers) that traverse a data structure — usually an array or string — simultaneously. Instead of checking every pair of elements with two nested loops, you use the positions of these two pointers cleverly so that you only ever do one pass through the data.
The key insight is that in many problems, you can eliminate large chunks of the search space based on a comparison between what the pointers currently point at. This is why the approach is so powerful.
Think of two pointers like two fingers scanning a book — one from the left margin, one from the right. Depending on what each finger sees, you decide which one to move inward. You never need to compare every possible pair of pages.
The Three Main Flavors
Opposite Ends (Converging)
Left pointer starts at index 0, right pointer starts at the last index. They move toward each other. Classic for sorted-array pair problems.
Same Direction (Sliding)
Both pointers start at index 0 and move forward. The gap between them forms a window. Classic for subarray/substring problems.
Fast & Slow (Floyd's)
One pointer moves one step at a time; the other moves two. Classic for cycle detection in linked lists.
Multi-array / Two Sequences
One pointer per array. Used to merge or compare two sorted arrays simultaneously. Classic in merge sort and diff algorithms.
Example 1 — Two Sum in a Sorted Array
Problem: Given a sorted array and a target number, find two elements that add up to the target. Return their indices (1-indexed).
Array: [2, 7, 11, 15] | Target: 9
- Calculate
sum = arr[L] + arr[R] - If
sum == target→ found it ✅ - If
sum < target→ move L right - If
sum > target→ move R left
| Step | L | R | arr[L] | arr[R] | Sum | Action |
|---|---|---|---|---|---|---|
| 1 | 0 | 3 | 2 | 15 | 17 | sum > 9 → move R left |
| 2 | 0 | 2 | 2 | 11 | 13 | sum > 9 → move R left |
| 3 | 0 | 1 | 2 | 7 | 9 | ✅ sum == target → return [1, 2] |
Python Code — Two Sum (Sorted)
def two_sum_sorted(nums, target):
left = 0
right = len(nums) - 1
while left < right:
current_sum = nums[left] + nums[right]
if current_sum == target:
return [left + 1, right + 1]
elif current_sum < target:
left += 1
else:
right -= 1
return []
nums = [2, 7, 11, 15]
target = 9
print(two_sum_sorted(nums, target)) # Output: [1, 2]
Example 2 — Valid Palindrome
Problem: Given a string, check whether it reads the same forwards and backwards (ignoring non-alphanumeric characters and case).
Input: "A man, a plan, a canal: Panama" → Output: True
def is_palindrome(s):
left = 0
right = len(s) - 1
while left < right:
while left < right and not s[left].isalnum():
left += 1
while left < right and not s[right].isalnum():
right -= 1
if s[left].lower() != s[right].lower():
return False
left += 1
right -= 1
return True
print(is_palindrome("A man, a plan, a canal: Panama")) # True
print(is_palindrome("race a car")) # False
Example 3 — Remove Duplicates from Sorted Array
Problem: Given a sorted array, remove duplicates in-place so that each element appears only once. Return the new length.
def remove_duplicates(nums):
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 + 1
nums = [1, 1, 2, 3, 3, 3, 4, 5, 5]
k = remove_duplicates(nums)
print(k) # 5
print(nums[:k]) # [1, 2, 3, 4, 5]
| fast | slow | nums[fast] | nums[slow] | Action |
|---|---|---|---|---|
| 1 | 0 | 1 | 1 | Same → skip |
| 2 | 0 | 2 | 1 | Different → slow=1, write 2 |
| 3 | 1 | 3 | 2 | Different → slow=2, write 3 |
| 4 | 2 | 3 | 3 | Same → skip |
| 5 | 2 | 3 | 3 | Same → skip |
| 6 | 2 | 4 | 3 | Different → slow=3, write 4 |
| 7 | 3 | 5 | 4 | Different → slow=4, write 5 |
| 8 | 4 | 5 | 5 | Same → skip |
Example 4 — Container With Most Water
Problem: Given an array height, find two lines that form a container holding the most water.
Key insight: Area = min(height[L], height[R]) × (R - L). Move the shorter side inward.
def max_water(height):
left = 0
right = len(height) - 1
max_area = 0
while left < right:
width = right - left
current_height = min(height[left], height[right])
area = width * current_height
max_area = max(max_area, area)
if height[left] < height[right]:
left += 1
else:
right -= 1
return max_area
height = [1, 8, 6, 2, 5, 4, 8, 3, 7]
print(max_water(height)) # Output: 49
Why move the shorter side? Because if you move the taller side, the width decreases AND the height stays limited by the same shorter side — area can only get worse. Moving the shorter side is the only chance to find a taller wall that could compensate for the reduced width.
When to Apply the Two Pointer Technique
- The input is sorted (or can be sorted) — Sorted order lets you make intelligent decisions about which pointer to move.
- You need to find a pair or triplet satisfying a condition — Two Sum, Three Sum, pair with max product.
- You need to check a symmetry property — Palindromes, balanced strings.
- You need to partition or filter in-place — Moving zeros, removing duplicates.
- You're optimizing a window or range — Container With Most Water, trapping rainwater.
- You're dealing with a linked list cycle — Fast/slow pointer finds cycles in O(1) space.
- You're merging two sorted arrays — One pointer per array.
Good Fit
Find if any pair sums to K in a sorted array, check palindrome, merge sorted arrays, detect linked list cycle, remove duplicates in-place.
Not a Fit
Problems needing random access or hash-based lookups, unsorted data where sorting changes the answer, tree/graph traversals.
Bonus — 3Sum (Extending Two Pointers)
def three_sum(nums):
nums.sort()
result = []
for i in range(len(nums) - 2):
if i > 0 and nums[i] == nums[i - 1]:
continue
left = i + 1
right = len(nums) - 1
while left < right:
total = nums[i] + nums[left] + nums[right]
if total == 0:
result.append([nums[i], nums[left], nums[right]])
while left < right and nums[left] == nums[left + 1]:
left += 1
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
nums = [-1, 0, 1, 2, -1, -4]
print(three_sum(nums)) # [[-1, -1, 2], [-1, 0, 1]]
Common Mistakes to Avoid
Applying two pointers to an unsorted array — it won't work unless you sort first. The logic of "move left to increase, move right to decrease" only holds in sorted order.
Forgetting the loop termination condition left < right. If both pointers reach the same index, you shouldn't be comparing an element with itself.
Not skipping duplicate values in 3Sum / K-Sum problems — this will give you duplicate triplets in the result.
Confusing two pointers with sliding window — both use two indices, but sliding window expands/contracts based on a constraint, not a fixed comparison rule.
Quick Reference — Problems to Practice
| Problem | Pattern | Difficulty |
|---|---|---|
| Two Sum II (sorted input) | Converging | Easy |
| Valid Palindrome | Converging | Easy |
| Remove Duplicates from Sorted Array | Same Direction | Easy |
| Move Zeros | Same Direction | Easy |
| Container With Most Water | Converging | Medium |
| 3Sum | Fix + Converging | Medium |
| Trapping Rain Water | Converging | Hard |
| Linked List Cycle Detection | Fast & Slow | Easy |
| Find Middle of Linked List | Fast & Slow | Easy |
| Minimum Window Substring | Sliding Window | Hard |
Wrapping Up
The two pointer technique is not a magic trick — it's a deliberate reduction of the search space. Every time you move a pointer, you're making a guarantee: "I know for certain this region can't hold a better answer." That guarantee comes from the structure of the data and the property you're searching for.
Once you internalize that idea, you'll start recognizing two-pointer problems on sight. Start with the easy problems in the practice table, implement each one from scratch, and trace through your pointer movements manually on paper at least once. That tactile understanding is what makes the technique click.
Pick any two problems from the practice table above. Implement them from scratch without looking at any code. Trace through your pointer movements step by step on paper first. If you can do that for three problems in a row, two pointers is officially in your toolkit.
Next up: explore the Sliding Window technique — two pointers' close cousin for subarray and substring optimization problems.