Skip to content
← Back to Blog

Two Pointers Technique Explained: From Basics to Advanced

11 min read

The Two Pointers technique is one of the most powerful and widely tested patterns in coding interviews. It replaces brute-force O(n²) nested loops with a single O(n) pass by using two index variables that traverse the data structure in a coordinated way. Whether you are preparing for FAANG interviews or grinding LeetCode, mastering Two Pointers will unlock dozens of problems instantly.

At its core, the idea is simple: instead of checking every possible pair with two nested loops, you place two pointers at strategic positions and move them based on a condition. Because each pointer moves at most n times, the total work is O(n) — a dramatic improvement over the O(n²) brute force.

Why O(n) Instead of O(n²)?

Consider the classic "find a pair that sums to a target" problem. The brute-force approach checks every pair:

# Brute force — O(n²)
def two_sum_brute(nums, target):
    for i in range(len(nums)):
        for j in range(i + 1, len(nums)):
            if nums[i] + nums[j] == target:
                return [i, j]
    return []

With a sorted array and two pointers, you eliminate half the search space with each comparison. The left pointer can only move right, and the right pointer can only move left — so the total number of steps is at most n, giving you O(n) time with O(1) extra space.

4 Types of Two Pointer Problems

Not all two-pointer problems look the same. Recognizing which variant a problem belongs to is the key to solving it quickly. Here are the four main types you will encounter in interviews.

Type 1: Opposite-End Pointers

Place one pointer at the start and one at the end of a sorted array. Move them toward each other based on how the current pair compares to the target.

Classic problems: Two Sum II (sorted input), Container With Most Water, Valid Palindrome, Trapping Rain Water

Type 2: Same-Direction (Slow & Fast) Pointers

Both pointers start at the beginning. A slow pointer tracks the position for the next valid element, while a fast pointer scans ahead. This is ideal for in-place array modifications.

Classic problems: Remove Duplicates from Sorted Array, Remove Element, Move Zeroes

Type 3: Two Arrays / Two Strings

Use one pointer per input. Advance the pointer whose current element is smaller (or matches a condition). This is the backbone of the merge step in merge sort.

Classic problems: Merge Sorted Arrays, Intersection of Two Arrays II, Is Subsequence

Type 4: Partition Pointers

Use two (or three) pointers to partition an array into regions based on a condition. Elements are swapped into the correct partition as the pointers sweep through.

Classic problems: Sort Colors (Dutch National Flag), Partition Array by Odd/Even, Quickselect Partition

When to Use Two Pointers

Look for these signals in the problem statement — any one of them is a strong hint that Two Pointers will work:

  • Sorted input — a sorted array or the ability to sort without violating constraints
  • Pair or triplet finding — "find two elements that..." or "find three elements that..."
  • In-place modification — "remove duplicates in place" or "move zeroes to end"
  • Comparing from both ends — palindrome checks, container/area problems
  • Merging sorted data — two sorted arrays or lists that need to be combined
  • O(1) space requirement — problems that forbid extra data structures

Want to practice recognizing these signals? Try the interactive Two Pointers pattern page with animated walkthroughs.

Step-by-Step Examples

Example 1: Two Sum II — Input Array Is Sorted

Problem: Given a 1-indexed sorted array numbers and a target, find two numbers that add up to the target. Return their 1-indexed positions.

Approach: Since the array is sorted, we use opposite-end pointers. If the sum is too small, move the left pointer right to increase it. If too large, move the right pointer left to decrease it.

def two_sum_ii(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 bigger sum
        else:
            right -= 1  # need a smaller sum

    return []  # no solution found

# Example walkthrough:
# numbers = [2, 7, 11, 15], target = 9
#
# Step 1: left=0, right=3 → 2 + 15 = 17 > 9 → right -= 1
# Step 2: left=0, right=2 → 2 + 11 = 13 > 9 → right -= 1
# Step 3: left=0, right=1 → 2 + 7  = 9  ✓ → return [1, 2]

Time: O(n) · Space: O(1) — each pointer moves at most n times, and we use no extra memory.

Example 2: 3Sum — Building on Two Sum II

Problem: Find all unique triplets in an array that sum to zero.

Key insight: Sort the array, then fix one element and use Two Sum II on the remaining portion. Skip duplicates to avoid repeated triplets.

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

        left, right = i + 1, len(nums) - 1
        target = -nums[i]

        while left < right:
            current_sum = nums[left] + nums[right]

            if current_sum == target:
                result.append([nums[i], nums[left], nums[right]])

                # Skip duplicates for second and third elements
                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 current_sum < target:
                left += 1
            else:
                right -= 1

    return result

# nums = [-1, 0, 1, 2, -1, -4]
# After sort: [-4, -1, -1, 0, 1, 2]
#
# i=0, nums[i]=-4, target=4: no pair sums to 4
# i=1, nums[i]=-1, target=1: left=2(-1), right=5(2) → 1 ✓ → [-1,-1,2]
#                              left=3(0), right=4(1) → 1 ✓ → [-1,0,1]
# i=2, nums[i]=-1: skip (duplicate of i=1)

Time: O(n²) · Space: O(1) — the outer loop runs n times, and each inner Two Pointers pass is O(n). This is optimal for 3Sum.

Example 3: Container With Most Water

Problem: Given an array of heights, find two lines that form a container holding the most water.

Why Two Pointers works: Start with the widest container (pointers at both ends). The area is limited by the shorter line. Moving the shorter pointer inward is the only way to potentially find a taller line and increase the area.

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)

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

    return max_water

# height = [1, 8, 6, 2, 5, 4, 8, 3, 7]
#
# left=0(1), right=8(7) → area = 8×1 = 8   → move left
# left=1(8), right=8(7) → area = 7×7 = 49  → move right
# left=1(8), right=7(3) → area = 6×3 = 18  → move right
# left=1(8), right=6(8) → area = 5×8 = 40  → move right
# ...
# max_water = 49

Time: O(n) · Space: O(1) — a single pass with two pointers converging from both ends.

Common Mistakes to Avoid

Mistake 1: Forgetting to Skip Duplicates

In problems like 3Sum, failing to skip duplicate values leads to repeated triplets in the result. Always add while left < right and nums[left] == nums[left + 1] checks after finding a valid pair.

Mistake 2: Moving the Wrong Pointer

In Container With Most Water, you must move the pointer with the shorter line. Moving the taller pointer can never increase the area because the width decreases and the height is still bounded by the shorter line. Getting this logic backwards produces wrong answers.

Mistake 3: Using Two Pointers on Unsorted Arrays

The converging (opposite-end) Two Pointers technique relies on sorted order. If the input is not sorted and you cannot sort it (e.g., you need original indices), use a hash map instead. Always check whether sorting is allowed.

Mistake 4: Off-by-One Errors in Loop Conditions

Using while left <= right instead of while left < right can cause infinite loops or comparing an element with itself. For pair-finding problems, the pointers should never overlap.

Two Pointers vs Sliding Window

Both patterns use two indices, but they solve fundamentally different problems. Here is the quick rule of thumb:

CriteriaTwo PointersSliding Window
Pointer movementConverge from both ends (or slow/fast)Both move left to right
InputUsually sortedAny order
GoalPairs, partitions, in-place opsOptimal contiguous subarray/substring
Keywords"pair", "sorted", "palindrome""longest", "shortest", "contiguous"

For a deep dive with side-by-side code comparisons, read Two Pointers vs Sliding Window: When to Use Each. If you want to master the Sliding Window pattern itself, check out How to Identify Sliding Window Problems.

10 Practice Problems (by Difficulty)

Work through these in order. Each problem reinforces a different variant of the Two Pointers technique.

Easy

  1. Two Sum II — Input Array Is Sorted (LC 167) — opposite-end pointers, the foundation
  2. Valid Palindrome (LC 125) — opposite-end, character comparison
  3. Remove Duplicates from Sorted Array (LC 26) — same-direction slow/fast pointers
  4. Merge Sorted Array (LC 88) — two-array pointers, merge from the end

Medium

  1. 3Sum (LC 15) — fix one element + Two Sum II with duplicate skipping
  2. Container With Most Water (LC 11) — opposite-end, greedy pointer movement
  3. Sort Colors (LC 75) — Dutch National Flag, three-way partition
  4. 4Sum (LC 18) — extends 3Sum with an additional outer loop
  5. 3Sum Closest (LC 16) — opposite-end with closest-sum tracking

Hard

  1. Trapping Rain Water (LC 42) — opposite-end with running max from both sides

Bonus: The Dutch National Flag Algorithm

The partition variant of Two Pointers deserves a quick code example. LeetCode 75 (Sort Colors) asks you to sort an array of 0s, 1s, and 2s in-place with a single pass:

def sort_colors(nums):
    low, mid, high = 0, 0, len(nums) - 1

    while mid <= high:
        if nums[mid] == 0:
            nums[low], nums[mid] = nums[mid], nums[low]
            low += 1
            mid += 1
        elif nums[mid] == 1:
            mid += 1
        else:  # nums[mid] == 2
            nums[mid], nums[high] = nums[high], nums[mid]
            high -= 1
    # After: all 0s before low, all 1s between low..high, all 2s after high

# [2, 0, 2, 1, 1, 0] → [0, 0, 1, 1, 2, 2]

Three pointers ( low, mid, high) partition the array into four regions: 0s, 1s, unexplored, and 2s. Each element is visited at most once — O(n) time, O(1) space.

Two Pointers Cheat Sheet

Sorted + target sum → Opposite-end pointers, move based on sum comparison

Remove/deduplicate in place → Slow/fast same-direction pointers

Merge two sorted inputs → One pointer per input, advance the smaller

Partition by value → Dutch National Flag (three-pointer swap)

Contiguous subarray / substring → Not Two Pointers — use Sliding Window instead

The Two Pointers technique is deceptively simple but incredibly versatile. Once you internalize the four variants — opposite-end, same-direction, two-array, and partition — you will be able to recognize and solve these problems within minutes during an interview.

For an interactive, animated walkthrough of exactly how the pointers move through real data, visit the Two Pointers pattern page on AlgoArk.

Ready to master every pattern?

Two Pointers is just one of 15+ patterns that appear in coding interviews. Explore them all with animated walkthroughs, then test yourself with the pattern recognition quiz.