Skip to main content

šŸ‘†šŸ‘† Two Pointers Pattern

The gateway pattern to coding interviews. Master this first.

TL;DR​

Two pointers uses two indices to traverse data structures, reducing O(n²) brute force to O(n).

Sorted array + find pair = Two Pointers

The Three Variants​

1. Opposite Ends (Most Common)​

Left ────────────────► Middle ◄──────────────── Right
L R

When to use: Sorted array, find pair, palindrome, containers

2. Same Direction​

Slow ─────► Fast ─────────────────►
S F

When to use: Remove duplicates, partition, merge sorted

3. Fast/Slow (Floyd's)​

○──○──○──○──○──○
S F
(slow) (fast: 2x speed)

When to use: Cycle detection, find middle


Pattern 1: Opposite Ends​

Template​

def two_pointer_opposite(arr, target):
left, right = 0, len(arr) - 1

while left < right:
current = arr[left] + arr[right]

if current == target:
return [left, right]
elif current < target:
left += 1 # Need bigger sum
else:
right -= 1 # Need smaller sum

return []

Problem: Two Sum II (Sorted Array)​

Given sorted array, find two numbers that add to target.

def two_sum(numbers: list[int], target: int) -> list[int]:
left, right = 0, len(numbers) - 1

while left < right:
total = numbers[left] + numbers[right]

if total == target:
return [left + 1, right + 1] # 1-indexed
elif total < target:
left += 1
else:
right -= 1

return []

# Example
# numbers = [2, 7, 11, 15], target = 9
# left=0 (2), right=3 (15): 17 > 9, right--
# left=0 (2), right=2 (11): 13 > 9, right--
# left=0 (2), right=1 (7): 9 == 9, return [1, 2]

Why it works:

  • Sum too small → need bigger number → move left right
  • Sum too big → need smaller number → move right left
  • Sorted array guarantees we don't miss any pair

Interactive Demo​

Two Sum II (Sorted Array)

L↓
R↓
2
7
11
15
0
1
2
3
Find two numbers that sum to 9. Start with pointers at both ends.
left=0, right=3
1 / 7
View Code
def two_sum(numbers, target):
    left, right = 0, len(numbers) - 1
    
    while left < right:
        total = numbers[left] + numbers[right]
        
        if total == target:
            return [left, right]
        elif total < target:
            left += 1
        else:
            right -= 1
    
    return []

Problem: Valid Palindrome​

Check if string is palindrome (letters/digits only).

def is_palindrome(s: str) -> bool:
left, right = 0, len(s) - 1

while left < right:
# Skip non-alphanumeric
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

# "A man, a plan, a canal: Panama" → True
# "race a car" → False

Interactive Demo​

Valid Palindrome

L↓
R↓
r
a
c
e
c
a
r
0
1
2
3
4
5
6
Check if "racecar" is a palindrome. Compare from both ends.
1 / 8
View Code
def is_palindrome(s):
    left, right = 0, len(s) - 1
    
    while left < right:
        if s[left].lower() != s[right].lower():
            return False
        left += 1
        right -= 1
    
    return True

Problem: Container With Most Water​

Find max water between two lines.

|         |      |
| | | | |
| | | | | | | |
| | | | | | | |
0 1 2 3 4 5 6 7
def max_area(height: list[int]) -> int:
left, right = 0, len(height) - 1
max_water = 0

while left < right:
width = right - left
h = min(height[left], height[right])
max_water = max(max_water, width * h)

# Move the shorter line inward
if height[left] < height[right]:
left += 1
else:
right -= 1

return max_water

Key insight: Always move the shorter line. Moving the taller one can only decrease (or maintain) area.

Interactive Demo​

Container With Most Water

L↓
R↓
1
8
6
2
5
4
8
3
7
0
1
2
3
4
5
6
7
8
Find max water container. Start with widest possible container.
1 / 18
View Code
def max_area(height):
    left, right = 0, len(height) - 1
    max_water = 0
    
    while left < right:
        width = right - left
        h = min(height[left], height[right])
        max_water = max(max_water, width * h)
        
        if height[left] < height[right]:
            left += 1
        else:
            right -= 1
    
    return max_water

Pattern 2: Same Direction​

Template​

def two_pointer_same_direction(arr):
slow = 0

for fast in range(len(arr)):
if condition(arr[fast]):
arr[slow] = arr[fast]
slow += 1

return slow # New length

Problem: Remove Duplicates from Sorted Array​

In-place removal, return new length.

def remove_duplicates(nums: list[int]) -> int:
if not nums:
return 0

slow = 1 # Position for next unique element

for fast in range(1, len(nums)):
if nums[fast] != nums[fast - 1]:
nums[slow] = nums[fast]
slow += 1

return slow

# [1, 1, 2, 2, 3] → [1, 2, 3, _, _], returns 3
Before: [1, 1, 2, 2, 3]
S F nums[F] == nums[F-1], skip
S F nums[F] != nums[F-1], copy & move S
S F skip
S F copy & move S
After: [1, 2, 3, 2, 3]
^^^^^^^ valid

Problem: Move Zeroes​

Move all 0s to end, maintain order.

def move_zeroes(nums: list[int]) -> None:
slow = 0

for fast in range(len(nums)):
if nums[fast] != 0:
nums[slow], nums[fast] = nums[fast], nums[slow]
slow += 1

# [0, 1, 0, 3, 12] → [1, 3, 12, 0, 0]

Pattern 3: Fast/Slow (Floyd's)​

Problem: Linked List Cycle​

Detect if cycle exists.

def has_cycle(head) -> bool:
slow = fast = head

while fast and fast.next:
slow = slow.next # Move 1 step
fast = fast.next.next # Move 2 steps

if slow == fast:
return True

return False

Why it works: If there's a cycle, fast eventually "laps" slow. If no cycle, fast hits null.


Problem: Find Middle of Linked List​

def find_middle(head):
slow = fast = head

while fast and fast.next:
slow = slow.next
fast = fast.next.next

return slow

# 1 → 2 → 3 → 4 → 5
# When fast reaches 5, slow is at 3

Advanced: 3Sum​

The classic FAANG problem.

Find all triplets that sum to zero.

def three_sum(nums: list[int]) -> list[list[int]]:
nums.sort() # Critical: enables two pointers
result = []

for i in range(len(nums) - 2):
# Skip duplicate first elements
if i > 0 and nums[i] == nums[i - 1]:
continue

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
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

# [-1, 0, 1, 2, -1, -4]
# Sorted: [-4, -1, -1, 0, 1, 2]
# Output: [[-1, -1, 2], [-1, 0, 1]]

Time: O(n²) - O(n log n) sort + O(n) for each of n elements Space: O(1) excluding output


Decision Flowchart​


Practice Problems (Grind 75)​

ProblemDifficultyPattern
Valid PalindromeEasyOpposite ends
Two Sum IIMediumOpposite ends
3SumMediumFix + opposite ends
Container With Most WaterMediumOpposite ends
Remove DuplicatesEasySame direction
Move ZeroesEasySame direction
Linked List CycleEasyFast/slow
Middle of Linked ListEasyFast/slow
Linked List Cycle IIMediumFast/slow + math
Trapping Rain WaterHardOpposite ends

Quick Reference​

VariantWhen to UseTemplate
OppositeSorted + pair searchleft=0, right=n-1
SameIn-place modifyslow for placing, fast for scanning
Fast/SlowLinked listslow=1x, fast=2x

Key insight: Two pointers reduce O(n²) → O(n) by eliminating redundant comparisons.



Next: Sliding Window → - For subarray/substring problems.