LeetCode Two Pointer Patterns: The Complete Guide
Master every two pointer pattern for LeetCode: opposite-end, same-direction, and fast/slow pointers with templates, complexity analysis, and 15+ mapped problems.
What you'll learn
- ✓The three core two pointer patterns and when each applies
- ✓Opposite-end pointers for sorted arrays and palindromes
- ✓Same-direction pointers for sliding window and partitioning
- ✓Fast/slow pointers for cycle detection and linked list problems
- ✓Templates with complexity analysis for each pattern
Prerequisites
- •Basic array and linked list operations
- •Understanding of O(n) vs O(n log n) complexity
- •Python fundamentals
Two pointers is one of the most versatile techniques in algorithm interviews. The core idea is simple: instead of brute-forcing with nested loops (O(n^2)), you maintain two indices that move through the data structure in a coordinated way, reducing the problem to O(n). This guide covers every major variant.
Pattern 1: Opposite-End Pointers
Place one pointer at the start and one at the end. Move them toward each other based on a condition. This pattern works on sorted arrays and palindrome checks.
Template
def opposite_end(arr: list[int]) -> ...:
left, right = 0, len(arr) - 1
while left < right:
if condition(arr[left], arr[right]):
# found answer or update result
left += 1
right -= 1
elif need_bigger_value:
left += 1
else:
right -= 1
Complexity: O(n) time, O(1) space.
Example: Two Sum II (LeetCode #167)
Given a 1-indexed sorted array, find two numbers that sum to a target.
def two_sum(numbers: list[int], target: int) -> list[int]:
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
else:
right -= 1
return []
print(two_sum([2, 7, 11, 15], 9)) # [1, 2]
print(two_sum([2, 3, 4], 6)) # [1, 3]
Example: Container With Most Water (LeetCode #11)
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)
# move the shorter side inward
if height[left] < height[right]:
left += 1
else:
right -= 1
return best
print(max_area([1, 8, 6, 2, 5, 4, 8, 3, 7])) # 49
Mapped Problems
| Problem | Key Insight |
|---|---|
| #167 Two Sum II | Sum too small? Move left. Too big? Move right. |
| #11 Container With Most Water | Always move the shorter wall inward. |
| #15 3Sum | Fix one element, run two pointers on the rest. |
| #125 Valid Palindrome | Compare characters from both ends. |
| #42 Trapping Rain Water | Track max height from each side. |
Pattern 2: Same-Direction Pointers (Slow/Fast Reader-Writer)
Both pointers start at the beginning. One (fast) scans every element; the other (slow) tracks the write position or window boundary. This pattern handles in-place deduplication, partitioning, and sliding windows.
Template: Remove Duplicates
def remove_duplicates(nums: list[int]) -> int:
if not nums:
return 0
slow = 0 # write pointer
for fast in range(1, len(nums)):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
return slow + 1
arr = [0, 0, 1, 1, 1, 2, 2, 3, 3, 4]
k = remove_duplicates(arr)
print(arr[:k]) # [0, 1, 2, 3, 4]
Example: Move Zeroes (LeetCode #283)
def move_zeroes(nums: list[int]) -> None:
slow = 0 # position for next non-zero
for fast in range(len(nums)):
if nums[fast] != 0:
nums[slow], nums[fast] = nums[fast], nums[slow]
slow += 1
arr = [0, 1, 0, 3, 12]
move_zeroes(arr)
print(arr) # [1, 3, 12, 0, 0]
Mapped Problems
| Problem | Key Insight |
|---|---|
| #26 Remove Duplicates from Sorted Array | Slow writes unique values, fast scans ahead. |
| #27 Remove Element | Same reader-writer; skip target value. |
| #283 Move Zeroes | Swap non-zeros to the slow pointer position. |
| #75 Sort Colors | Dutch National Flag: three pointers (low, mid, high). |
| #80 Remove Duplicates II | Allow at most two of each; track count. |
Pattern 3: Fast/Slow (Floyd’s Tortoise and Hare)
One pointer moves one step at a time; the other moves two. If there is a cycle, they will meet. This pattern is essential for cycle detection in linked lists and for finding the middle node.
Template: Cycle Detection
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def has_cycle(head: ListNode) -> bool:
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow is fast:
return True
return False
Why it works: In a cycle, the fast pointer gains one step per iteration. The gap between them decreases by one each time, so they must eventually collide.
Example: Find the Cycle Start (LeetCode #142)
def detect_cycle(head: ListNode) -> ListNode:
slow = fast = head
# phase 1: detect cycle
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow is fast:
break
else:
return None # no cycle
# phase 2: find entry point
slow = head
while slow is not fast:
slow = slow.next
fast = fast.next
return slow
The math behind phase 2: if the distance from head to cycle start is a, and the meeting point is b steps into the cycle, then a = c where c is the remaining cycle length after the meeting point.
Example: Find Middle of Linked List (LeetCode #876)
def middle_node(head: ListNode) -> ListNode:
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow # slow is at the middle
Mapped Problems
| Problem | Key Insight |
|---|---|
| #141 Linked List Cycle | Basic fast/slow detection. |
| #142 Linked List Cycle II | Two-phase: detect, then find entry. |
| #876 Middle of the Linked List | When fast reaches end, slow is at middle. |
| #202 Happy Number | Treat the sequence as a linked list; detect cycle. |
| #287 Find the Duplicate Number | Array as implicit linked list; Floyd’s algorithm. |
Choosing the Right Pattern
Use this decision tree:
- Sorted array + searching for a pair? Opposite-end pointers.
- In-place modification or partitioning? Same-direction reader-writer.
- Cycle detection or finding a midpoint? Fast/slow pointers.
- Substring or subarray with a constraint? Sliding window (same-direction variant).
Complexity Summary
| Pattern | Time | Space | Typical Use |
|---|---|---|---|
| Opposite-end | O(n) | O(1) | Pair search, palindromes |
| Same-direction | O(n) | O(1) | Dedup, partition, filter |
| Fast/slow | O(n) | O(1) | Cycles, midpoints |
Common Pitfalls
Off-by-one errors: Always clarify whether your loop condition is left < right or left <= right. For pair-finding, use strict < to avoid using the same element twice.
Forgetting to sort: Opposite-end pointers require sorted input. If the input is unsorted, sort first (O(n log n)) or use a hash set instead.
Infinite loops: Make sure at least one pointer moves in every iteration. If your condition does not advance either pointer, the loop never terminates.
Two pointers is a pattern family, not a single trick. Once you recognize which variant applies, the implementation follows a predictable template. Practice mapping problems to these three categories and the solutions will come naturally.
Related articles
- LeetCode Binary Search Patterns: Search Space, Boundaries, and Rotated Arrays
Master binary search patterns for LeetCode including search space reduction, boundary finding, rotated array search, and practical templates.
- LeetCode Sliding Window Patterns: Fixed, Variable, and Two Pointer Variants
Master the sliding window technique with fixed and variable window patterns, two pointer variants, and reusable templates for solving LeetCode problems.
- LeetCode LeetCode Interval Problems: Merge, Insert, and Schedule
Master interval problems on LeetCode: sorting, merging, inserting, and scheduling with templates, visual walkthroughs, and complexity analysis.
- LeetCode Dynamic Programming Patterns: 1D, 2D, Memoization, and Tabulation
Master dynamic programming with 1D and 2D patterns, memoization vs tabulation approaches, and solutions to classic LeetCode DP problems.