62 questions using this pattern
Use two pointers to traverse data from different positions, often moving toward or away from each other. This technique transforms O(n²) brute force into O(n) by eliminating redundant comparisons.
Imagine two people reading a book from opposite ends, each moving toward the middle. The person at the back skips ahead when they find what they're looking for, while the person at the front moves forward when they don't match. They meet somewhere in the middle, having searched the entire book without either person reading the same page twice.
Another way to think about it: squeezing toothpaste from both ends of the tube. You apply pressure from each side, working toward the center until you've gotten everything out.
The two pointers technique eliminates the need for nested loops by maintaining two positions that move through the data based on conditions.
The key insight is that when data has structure (like being sorted), you can make intelligent decisions about which pointer to move. If the current pair is too small, moving the left pointer right increases the sum. If it's too large, moving the right pointer left decreases it.
This reduces O(n²) brute force (checking all pairs) to O(n) because each element is visited at most twice—once by each pointer.
def two_sum(nums: list[int], target: int) -> list[int]: left = 0 right = len(nums) - 1 while left < right: total = nums[left] + nums[right] if total == target: return [left, right] elif total < target: left += 1 else: right -= 1 return [] # No solution foundWe have a sorted array [2, 7, 11, 15] and need to find two numbers that add up to 9.
Example: Find pair with sum = 10 in sorted array
Array: [1, 2, 4, 6, 8, 10] Target: 10
L R
Step 1: 1 + 10 = 11 > 10 → Sum too large, move R left
L R
Step 2: 1 + 8 = 9 < 10 → Sum too small, move L right
L R
Step 3: 2 + 8 = 10 ✓ → Found! Return [1, 4]
Key insight: Because the array is sorted, we know exactly which pointer to move. Too big? Decrease the larger value. Too small? Increase the smaller.
Use this skeleton as a starting point for problems using this pattern:
def two_pointers(arr: list, target: int) -> list:
"""Two pointers converging from opposite ends."""
left, right = 0, len(arr) - 1
while left < right:
current = arr[left] + arr[right]
if current == target:
return [left, right] # Found!
elif current < target:
left += 1 # Need larger sum
else:
right -= 1 # Need smaller sum
return [] # No solution found
def two_pointers_same_direction(arr: list) -> int:
"""Two pointers moving in same direction (slow/fast)."""
slow = 0
for fast in range(len(arr)):
if some_condition(arr[fast]):
arr[slow] = arr[fast]
slow += 1
return slow # New lengthLook for these keywords and patterns in problem descriptions:
Using <= instead of < when pointers should not overlap causes
infinite loops or double-counting elements.
Fix:
For converging pointers, use while left < right. Only use <= when
the same element can be part of the answer twice.
When the problem asks for unique pairs, forgetting to skip duplicate values leads to repeated answers.
Fix:
After finding a match, skip over duplicates:
while left < right and arr[left] == arr[left + 1]:
left += 1
Pointers start at opposite ends and move toward each other. Used for pair problems in sorted arrays.
Examples: Two Sum II, Container With Most Water, Valid Palindrome
Both pointers start at the same end but move at different speeds or based on different conditions. Used for in-place modifications.
Examples: Remove Duplicates, Move Zeros, Remove Element
Two pointers defining a window that expands and contracts. Technically a separate pattern but uses similar mechanics.
Examples: Minimum Window Substring, Longest Substring Without Repeating
Extension with three pointers for problems involving triplets or partitioning into three sections.
Examples: 3Sum, Sort Colors (Dutch National Flag)
Start here to build foundational understanding.
Master the pattern with these representative problems.
Test your mastery with complex variations.
Explore similar techniques:
Efficiently search sorted data by repeatedly dividing the search space in half. This transforms O(n) linear search into O(log n) by eliminating half the remaining possibilities with each comparison.
Use two pointers moving at different speeds to detect cycles, find midpoints, or identify patterns in sequences. The fast pointer advances twice as quickly, allowing detection of structural properties without extra space.
Maintain a window of elements that slides through the data, tracking a constraint or computing aggregates. This transforms O(n*k) brute force into O(n) by incrementally updating the window instead of recalculating from scratch.
Moving both pointers simultaneously after finding a match can skip valid solutions.
Fix:
Move one pointer at a time and let the next iteration decide the other. After a match, move both only when you've recorded the result.
Two pointers only works predictably on sorted data. Applying it to unsorted arrays gives wrong results.
Fix:
Sort first if needed (adds O(n log n)), or use a hash map approach instead if sorting changes the problem semantics.