Home/Blog/Two Pointers Technique: When to Use It, 4 Variants & How to Spot It Fast
two pointersDSAcoding interview10 min read

Two Pointers Technique: When to Use It and How to Spot It

The two pointers technique reduces O(n²) brute force solutions to O(n) by using two indices that move through an array or string in a coordinated way. It is one of the most frequently tested patterns in FAANG coding interviews.


When to Use Two Pointers

Use two pointers when:

  • The input is sorted (or can be sorted without losing information)
  • You need to find a pair or triplet satisfying a condition
  • You need to compare elements from both ends of an array
  • You need to remove duplicates in-place from a sorted array
  • The problem involves a string or array and asks for a subarray/substring

The Three Variants

Variant Movement Use Case
Opposite ends Left and right move toward each other Sorted array pairs, container problems
Same direction Both move left to right Removing duplicates, merging
Fast/slow One moves 1 step, other moves 2 steps Cycle detection, finding middle

The Trigger Pattern

The problem says "sorted array" and asks for a pair → opposite ends. The problem says "remove duplicates in-place" → same direction. The problem says "detect a cycle" → fast/slow.


Opposite Ends: Two Sum II (Sorted Array)

This is the entry-level two pointers problem. Given a sorted array and a target, find two numbers that add up to the target.

def two_sum_sorted(numbers, target):
    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  # Need a larger sum, move left pointer right
        else:
            right -= 1  # Need a smaller sum, move right pointer left

    return []

Why this works: Since the array is sorted, moving the left pointer right increases the sum, and moving the right pointer left decreases the sum. You can eliminate one pointer's position in each step, making this O(n).

Time: O(n). Space: O(1).


Opposite Ends: Container With Most Water

This problem asks you to find two lines that together with the x-axis form a container holding the most water. It is a classic two pointers problem where you must prove the greedy approach works.

def max_area(height):
    left, right = 0, len(height) - 1
    max_water = 0

    while left < right:
        # Water is limited by the shorter line
        width = right - left
        water = min(height[left], height[right]) * width
        max_water = max(max_water, water)

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

    return max_water

Why this works: Moving the taller line inward can never increase the area because the width decreases and the height is limited by the shorter line. Moving the shorter line gives a chance to find a taller line, potentially increasing the area.

Time: O(n). Space: O(1).


Opposite Ends: 3Sum

The 3Sum problem extends two pointers to triplets. Given an array, find all unique triplets that sum to zero. This is the most commonly asked two pointers variant at FAANG companies.

def three_sum(nums):
    nums.sort()
    result = []

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

        # Early termination: smallest sum too large
        if nums[i] > 0:
            break

        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 for second element
                while left < right and nums[left] == nums[left + 1]:
                    left += 1
                # Skip duplicates for third element
                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

Why this works: Sort the array first. Fix one element (nums[i]), then use two pointers on the remaining portion to find pairs that sum to -nums[i]. The duplicate skipping logic is critical — without it, you get duplicate triplets in the output.

Time: O(n²). Space: O(1) excluding output.


Same Direction: Remove Duplicates

Given a sorted array, remove duplicates in-place so each element appears only once. Return the new length. This uses the same-direction two pointers variant.

def remove_duplicates(nums):
    if not nums:
        return 0

    slow = 0  # Position of last unique element

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

    return slow + 1

Why this works: The slow pointer marks where the next unique element should go. The fast pointer scans ahead. When fast finds a new unique value, slow advances and copies it. This processes the array in one pass.

Time: O(n). Space: O(1).


Fast/Slow Pointers: Detect Cycle

Floyd's cycle detection algorithm uses two pointers moving at different speeds. If a cycle exists, the fast pointer eventually laps the slow pointer.

def has_cycle(head):
    if not head or not head.next:
        return False

    slow = head
    fast = head.next

    while slow != fast:
        if not fast or not fast.next:
            return False
        slow = slow.next
        fast = fast.next.next

    return True

Why this works: If there is no cycle, fast reaches the end (None). If there is a cycle, fast enters the cycle first and slow follows. Since fast gains one node per iteration, it will eventually catch slow from behind. The meeting point confirms the cycle.

Time: O(n). Space: O(1).


Common Mistakes

  1. Not handling duplicates in 3Sum. Without the duplicate skipping logic, you return duplicate triplets. Always check nums[i] == nums[i-1] before processing and skip matching values after finding a valid triplet.

  2. Moving the wrong pointer in container problem. Always move the shorter line. Moving the taller line can only decrease or maintain the area, never increase it.

  3. Using two pointers on unsorted input (when not appropriate). Two pointers on opposite ends requires sorted input. If the problem says "unsorted," you need a different approach (hash map, etc.) or you must sort first (which changes indices).

  4. Forgetting the fast pointer can overtake slow in cycle detection. Always check fast and fast.next for None before advancing. A common bug is accessing fast.next.next when fast.next is None.

  5. Not initializing pointers correctly. For opposite-end problems, start with left = 0 and right = len(nums) - 1. For same-direction problems, start both at 0 or handle the first element separately.


Practice Problems

Start with these problems to master two pointers:

  1. Two Sum II - Input Array Is Sorted — The entry-level opposite-end problem. Master this before moving to harder variants.
  2. Container With Most Water — Tests your ability to prove why the greedy approach works. Frequently asked at Amazon.
  3. 3Sum — The most common two pointers variant in FAANG interviews. Focus on duplicate handling.
  4. Remove Duplicates from Sorted Array — The classic same-direction two pointers problem. Tests in-place array manipulation.
  5. Linked List Cycle — The entry-level fast/slow pointer problem. Foundational for cycle-related questions.

Practice What You Learned

Ready to put this into practice? Try a mock coding interview with an AI interviewer who can give you two pointers problems and evaluate your approach in real time.


Frequently Asked Questions

Can I use two pointers on an unsorted array?

Not for opposite-end problems. Opposite-end two pointers relies on sorted order to decide which pointer to move. For unsorted arrays, you need a hash map or must sort first. However, fast/slow pointers (cycle detection) work on unsorted linked lists because they do not depend on element order.

How do I prove the two pointers approach is correct?

For each pointer movement, explain why it cannot skip the optimal solution. In container with most water, moving the taller line cannot improve the area because the height is bounded by the shorter line. In 3Sum, sorting and using two pointers is correct because the sorted order lets you move pointers based on whether the sum is too large or too small.

What's the difference between two pointers and sliding window?

Two pointers typically refers to two specific indices with coordinated movement (converging or same-direction). Sliding window is a special case where both pointers define a window and you maintain state (sum, frequency count) as the window expands or contracts. Sliding window problems usually involve a contiguous subarray or substring.

How do I handle the case where no solution exists?

For opposite-end problems, the while loop naturally terminates when the pointers cross. Return an empty result at that point. For 3Sum, the outer loop ends when i reaches len(nums) - 2. There is no special case needed — if no triplets exist, the result list stays empty.

Put it into practice

Interview with Alex

Real FAANG-style problems. Instant hiring signal. Free.

Start a Mock Interview →