LearnAbout

Two Pointers

On this page
Quick reference
Opposite-direction sweepO(n)
Same-direction (slow/fast) sweepO(n)
Brute-force pair check (for comparison)O(nΒ²)
The Idea

Picture two fingers resting on the same array, each free to move independently. The brute-force way to check every pair of elements is a nested loop β€” for each element, scan every other element again, which is O(nΒ²) work. Two pointers is the realization that for a lot of problems, you don't need every pair β€” you need the pairs to meet each other in a smart order, so each finger only needs to sweep across the array once.

There are two common ways to place the fingers. One starts them at opposite ends and walks them toward each other, converging in the middle. The other starts them together at the front and lets one race ahead of the other, both moving in the same direction. Which one applies depends on what the problem is actually asking β€” but both replace an O(nΒ²) nested scan with a single O(n) sweep.

The headline win is dropping from O(nΒ²) (check every pair) to O(n) (each pointer crosses the array once), using O(1) extra space β€” no auxiliary array or lookup structure needed, just a couple of index variables.
Build It
Two pointer shapes
// Opposite-direction: start at both ends, move inward
left = 0
right = length - 1
while left < right:
    ... look at arr[left] and arr[right] ...
    move left forward, or right backward, or both, depending on what you find

// Same-direction: both start at the front; one is the "slow" writer,
// one is the "fast" scanner racing ahead of it
slow = 0
for fast from 0 to length - 1:
    ... decide whether arr[fast] belongs at position slow ...
    if it does: place it there, then slow += 1
opposite-direction β€” converge from both ends
function hasPairWithSum(sortedArr, target):
    left = 0
    right = length(sortedArr) - 1
    while left < right:
        sum = sortedArr[left] + sortedArr[right]
        if sum == target:
            return true
        elif sum < target:
            left += 1        // sum too small β€” the only way up is a bigger left value
        else:
            right -= 1        // sum too big β€” the only way down is a smaller right value
    return false
Each step rules out an entire row or column of the pair grid, not just one pair β€” that's what collapses O(nΒ²) pair-checking into O(n).
same-direction β€” slow/fast in-place filter
function moveMatchingToFront(arr, matches):
    slow = 0
    for fast from 0 to length(arr) - 1:
        if matches(arr[fast]):
            swap(arr[slow], arr[fast])
            slow += 1
    return slow    // everything before this index matches; everything after doesn't
Fast explores, slow marks the last confirmed-good position. (Phase 2's Linked Lists topic reuses this exact slow/fast mechanic, walking .next pointers through a chain instead of indices through an array β€” same idea, different vehicle.)
Know It

Both pointer shapes do the same thing to the growth curve: replace a nested O(nΒ²) scan with a single pass where each pointer moves at most n steps total, using nothing but a couple of index variables.

OperationTimeSpaceWhy
Opposite-direction sweepO(n)O(1)left and right together cover at most n total steps before they cross β€” no element is ever revisited.
Same-direction (slow/fast) sweepO(n)O(1)fast visits every element exactly once; slow only ever moves forward, never past fast β€” combined work is still linear in n.
Brute-force pair check (for comparison)O(nΒ²)O(1)Checking every (i, j) pair with a nested loop is the cost two pointers exists to avoid.
Break It

Pointers crossing without a stop condition

the loop condition doesn't check left < right (or fast < length)

Forgetting the crossing check lets left and right sail past each other, or lets fast walk off the end of the array β€” an infinite loop in the first case, an out-of-bounds read in the second. The loop condition is not optional bookkeeping; it's the thing that guarantees termination.

"Skip duplicates" silently broken

the array has repeated values and a duplicate-skipping step is missing or off

Problems like 3Sum need to skip past repeated values after finding a match, or the same triplet gets reported multiple times. A duplicate-skip written as "skip while arr[i] == arr[i+1]" instead of "skip while arr[i] == arr[i-1]" (or applied to the wrong pointer) either skips too much and misses valid answers, or skips too little and reports duplicates.

Assuming sorted input the pattern actually requires

the opposite-direction sweep is applied to unsorted data

The opposite-direction pattern above only works because moving left forward strictly increases the sum and moving right backward strictly decreases it β€” a guarantee that only holds on sorted data. Running it on an unsorted array doesn't crash, it just silently returns wrong answers, because the "too small β†’ move left" logic no longer means what it assumes.

Use It
Two Sum II - Input Array Is Sorted
Sorted input means the two numbers you want sit somewhere between the extremes β€” start one finger at each end; if the sum's too big move the right finger in, too small move the left finger in.
Easy
3Sum
Sort the array, then fix one number at a time and solve the remaining two-number problem with the opposite-direction sweep from Two Sum II on what's left β€” skip past repeated values so the same triplet doesn't get reported twice.
Medium
Container With Most Water
Start with the widest possible container (both ends) and narrow inward. Always move the shorter wall β€” the taller one could never have been the bottleneck for a wider container anyway, so keeping it can only help.
Medium
Sort Colors
Three pointers instead of two: one marking the next spot for a low value, one marking the next spot for a high value, and a current scanner between them, swapping the current element into place as it moves.
Medium
Remove Duplicates from Sorted Array
One pointer scans ahead reading every element; a second, slower pointer marks the last confirmed-unique spot and only advances when the scanner finds a new value.
Easy
Squares of a Sorted Array
This is where naming it helps: two pointers at both ends of the sorted array β€” the largest square always comes from whichever end has the bigger absolute value β€” filling the answer array from the back inward.
Easy
Move Zeroes
The same same-direction two-pointer sweep from Build It: a slow pointer marks where the next non-zero belongs, a fast pointer scans ahead, and they swap when the fast pointer finds a non-zero.
Easy
Backspace String Compare
Walk both strings from the end backward with two independent pointers, skipping a character every time a '#' is seen. Comparing from the back resolves backspaces without ever building a new string.
Easy
Reverse Vowels of a String
The same opposite-direction sweep as Valid Palindrome, but only stop and swap when both pointers are sitting on vowels β€” every consonant just slides past untouched.
Easy
Valid Palindrome
The exact opposite-direction two-pointer sweep from Arrays & Strings Fundamentals, now with its real name β€” converge from both ends, skip non-alphanumeric characters, compare case-insensitively.
Easy