foreach-ui logo
codeLanguages
account_treeDSA

Quick Actions

quizlock Random Quiz
trending_uplock Progress
  • 1
  • 2
  • 3
  • 4
  • quiz
Algorithms
  • Understand the three main two pointer patterns: opposite ends, same direction, and fast/slow
  • Recognize when to apply two pointers versus nested loops or hash tables
  • Master common problems: finding pairs, removing duplicates, merging arrays
  • Learn to think in terms of pointer movement decisions and invariants

Introduction to Two Pointers Technique

Here's a problem: you have a sorted array and need to find two numbers that add up to a target. The naive approach checks every pair—that's O(n²). But there's a better way that feels almost too simple to work.

Start with one pointer at the beginning and one at the end. If the sum is too small, move the left pointer right. If it's too large, move the right pointer left. Keep going until you find the target or the pointers meet.

That's two pointers. It's not complicated, but it transforms problems that would normally require nested loops into single-pass solutions.

Why Two Pointers Works

Think about finding two numbers that sum to 10 in [1, 2, 3, 4, 6, 8, 9].

The brute force way:

for i in range(len(arr)):
    for j in range(i+1, len(arr)):
        if arr[i] + arr[j] == 10:
            return [i, j]

That's O(n²) comparisons. For n=100, that's 10,000 checks. For n=1000, a million.

The two pointers way:

left, right = 0, len(arr) - 1

while left < right:
    current_sum = arr[left] + arr[right]
    
    if current_sum == 10:
        return [left, right]
    elif current_sum < 10:
        left += 1  # Need a bigger number
    else:
        right -= 1  # Need a smaller number

That's O(n). For n=1000, that's at most 1000 checks. We've gone from a million to a thousand.

Why does this work? Because the array is sorted. When the sum is too small, we know the left pointer (pointing to the smallest remaining number) needs to move right. When it's too large, the right pointer (pointing to the largest remaining number) needs to move left. We're making an informed decision at each step, eliminating possibilities without checking them all.

The Three Patterns

Two pointers isn't one technique—it's a family of related approaches. Understanding the patterns helps you recognize when to use them.

Pattern 1: Opposite Ends (Converging Pointers)

Start at opposite ends, move toward each other. This works when you need to examine pairs and the data is sorted.

Finding pairs that sum to a target (we just saw this), checking if a string is a palindrome, container with most water problems—all use this pattern.

def is_palindrome(s):
    left, right = 0, len(s) - 1
    
    while left < right:
        if s[left] != s[right]:
            return False
        left += 1
        right -= 1
    
    return True

We check characters from both ends. If they don't match, it's not a palindrome. If they do, move inward. No need to check the whole string twice.

Pattern 2: Same Direction (Sliding Window)

Both pointers move in the same direction, maintaining a "window" of elements. The window can be fixed size or variable.

Finding the longest substring without repeating characters, maximum sum subarray of size k, minimum window containing target—these all use sliding window.

def longest_substring_without_repeating(s):
    left = 0
    seen = set()
    max_length = 0
    
    for right in range(len(s)):
        # Expand window by moving right pointer
        while s[right] in seen:
            # Shrink window from left if duplicate found
            seen.remove(s[left])
            left += 1
        
        seen.add(s[right])
        max_length = max(max_length, right - left + 1)
    
    return max_length

The right pointer explores new elements, the left pointer contracts when we violate the constraint (no repeating characters). The window between them represents our current candidate substring.

Pattern 3: Fast and Slow (Tortoise and Hare)

One pointer moves faster than the other. This is famous for cycle detection but has other uses.

Floyd's cycle detection (finding loops in linked lists), finding the middle element, detecting duplicates in certain constraints—fast and slow pointers.

def has_cycle(head):
    slow = fast = head
    
    while fast and fast.next:
        slow = slow.next        # Move one step
        fast = fast.next.next   # Move two steps
        
        if slow == fast:
            return True  # They met, there's a cycle
    
    return False  # Fast reached the end, no cycle

If there's a cycle, the fast pointer will eventually lap the slow pointer like a runner on a track. If there's no cycle, the fast pointer reaches the end.

When Two Pointers Applies

Not every problem needs two pointers. Here's what to look for:

Sorted data: Many two pointer techniques rely on sorted arrays. If the data isn't sorted but can be sorted without breaking the problem, that's a hint.

Pairs or triplets: Finding two or three elements with a specific relationship (sum, difference, product).

Subarrays or subsequences: Problems asking about contiguous or non-contiguous portions of an array.

Linear structures: Arrays, linked lists, strings. Two pointers doesn't work well on trees or graphs.

Optimization without extra space: If the problem asks for O(1) space and the brute force needs O(n), two pointers might help.

Common Problems and Patterns

Let's look at some classic examples to see the patterns in action.

Remove Duplicates from Sorted Array

You have [1, 1, 2, 2, 3, 4, 4] and need to remove duplicates in-place, returning the new length. The catch: do it in O(1) space.

Two pointers: one "slow" pointer tracks where the next unique element should go, one "fast" pointer scans for unique elements.

def remove_duplicates(arr):
    if not arr:
        return 0
    
    slow = 0  # Position for next unique element
    
    for fast in range(1, len(arr)):
        if arr[fast] != arr[slow]:
            slow += 1
            arr[slow] = arr[fast]
    
    return slow + 1  # Length of unique elements

Trace through [1, 1, 2, 2, 3]:

Initial: [1, 1, 2, 2, 3], slow=0

fast=1: arr[1]=1 == arr[0]=1, skip
fast=2: arr[2]=2 != arr[0]=1, slow=1, arr[1]=2 → [1, 2, 2, 2, 3]
fast=3: arr[3]=2 == arr[1]=2, skip  
fast=4: arr[4]=3 != arr[1]=2, slow=2, arr[2]=3 → [1, 2, 3, 2, 3]

Return slow+1 = 3

The first 3 elements are now [1, 2, 3].

Merge Two Sorted Arrays

You have [1, 3, 5] and [2, 4, 6]. Merge them into [1, 2, 3, 4, 5, 6].

Use two pointers, one for each array:

def merge(arr1, arr2):
    result = []
    i = j = 0
    
    while i < len(arr1) and j < len(arr2):
        if arr1[i] < arr2[j]:
            result.append(arr1[i])
            i += 1
        else:
            result.append(arr2[j])
            j += 1
    
    # Append remaining elements
    result.extend(arr1[i:])
    result.extend(arr2[j:])
    
    return result

At each step, we take the smaller of the two current elements. This is how merge sort's merge step works—O(n+m) time, linear in the total size.

Container With Most Water

You have an array representing heights [1, 8, 6, 2, 5, 4, 8, 3, 7]. Think of them as vertical lines. Which two lines, along with the x-axis, can contain the most water?

The area is min(height[left], height[right]) × (right - left). We want to maximize this.

Start with the widest container (left=0, right=end). The area is limited by the shorter of the two heights. To potentially get a larger area, we need a taller line, so move the pointer at the shorter height inward.

def max_area(heights):
    left, right = 0, len(heights) - 1
    max_water = 0
    
    while left < right:
        width = right - left
        height = min(heights[left], heights[right])
        max_water = max(max_water, width * height)
        
        # Move the pointer at the shorter height
        if heights[left] < heights[right]:
            left += 1
        else:
            right -= 1
    
    return max_water

Why move the shorter height? Because the area is limited by the shorter line. Moving the taller line inward can only decrease the width without helping the height, so it can't improve the area.

The Two Pointers Mindset

The key to using two pointers effectively is recognizing the pattern. Ask yourself:

Can I sort the data? Many two pointer techniques work on sorted data.

Do I need to examine pairs or subarrays? Two pointers excel at these.

Can I make a decision that eliminates possibilities? The power of two pointers is moving intelligently, not just checking everything.

Is there a constraint I can exploit? Sorted order, monotonicity, specific relationships between elements.

When you see a problem asking for pairs that sum to something, think opposite-end pointers. When it asks about subarrays, think sliding window. When it involves linked list cycles or finding middle elements, think fast and slow.

Common Mistakes

Moving pointers without logic: Each pointer movement should be based on the problem's constraints or the current state. Don't just move both forward unconditionally.

Forgetting boundary checks: Always ensure left < right (or left <= right depending on the problem). Off-by-one errors are common.

Assuming sorted data: If the problem doesn't say the data is sorted, and your solution depends on it, you might need to sort first (and account for that in complexity).

Using when not needed: Not every array problem needs two pointers. If you can solve it with a simple loop or a hash map more clearly, do that.

Why This Matters

Two pointers is a thinking tool. Once you internalize the patterns, you'll spot opportunities everywhere:

  • Interview questions that scream "two pointers" (and they're common)
  • Performance optimizations in real code
  • Understanding how standard library functions work (like array merging)

It's also a gateway to understanding more complex techniques. Sliding window is a variation of two pointers. Many DP optimizations use two pointer ideas. Binary search can be seen as a type of pointer manipulation.

Most importantly, two pointers teaches you to think about invariants—properties that remain true as your algorithm runs. This kind of thinking applies far beyond arrays and linked lists.

© 2026 forEach. All rights reserved.

Privacy Policy•Terms of Service