Skip to content
Two Pointers: Opposite and Same Direction with Examples

Two Pointers: Opposite and Same Direction with Examples

Two Pointers is the technique that turns a nested loop into a single pass. Instead of checking every pair of elements, which costs

O(N^2)
, you keep two indices into the same array or string and move them according to a rule. Each comparison lets you discard a whole group of candidates, so the work drops to
O(N)
.

The two directions matter and they solve different problems. Opposite direction pointers start at the two ends and close inward. That is how you find a pair with a target sum in sorted data, or check whether a string is a palindrome. Same direction pointers both move forward, usually at different speeds or with different jobs. That is how you rewrite an array in place, like removing duplicates or compacting values. Interviewers use both, and candidates who mix them up waste time in the interview.

Definition: two pointers is the practice of keeping two indices into one sequence and advancing at least one of them per step, based on a comparison, until the pointers meet a condition or exhaust the sequence.

Real-World Analogy

Picture two people folding a long banner toward the middle. One person holds the left edge, the other holds the right edge. Each fold pairs the two outermost layers, one from each end, and after the pair is folded both people move one step inward. When they meet in the middle, every layer has been paired exactly once, and no layer was ever touched twice. That is the opposite direction variant of two pointers. Two indices sit at the ends of the data and make one comparison per step, closing inward.

The same direction variant is more like reading with two bookmarks. One bookmark marks how far you have fully processed the page, the other scans ahead looking for something new. Every time the scanning bookmark finds a value the processed section does not have, you drag the first bookmark forward and note the value. Both bookmarks move left to right, but the processed one only advances when the scan finds something worth keeping.

Visual Explanation

The diagram below shows the opposite direction flow for finding a pair that sums to a target in a sorted array.

    graph TD
    I["left = 0, right = n - 1"] --> C{"left < right?"}
    C -->|Yes| S["Compare arr[left] + arr[right] to target"]
    S --> T{"Sum == target?"}
    T -->|"Yes"| F["Return the pair"]
    T -->|"Sum < target"| L["left++"]
    T -->|"Sum > target"| R["right--"]
    L --> C
    R --> C
    C -->|No| N["No pair exists"]
  

The part to notice is that exactly one pointer moves per step. If the sum is too small, only the left pointer can increase it, because a sorted array only gets larger to the right. If it is too large, only the right pointer can decrease it. Either way, one decision eliminates every remaining pair that contains the moved element, which is why the loop never revisits an index.

The same direction flow for removing duplicates from a sorted array looks like this.

    graph TD
    I["slow = 0, fast = 1"] --> C{"fast < n?"}
    C -->|Yes| D{"nums[fast] != nums[slow]?"}
    D -->|"Yes"| W["slow++, copy nums[fast] to nums[slow]"]
    D -->|"No"| X["Skip the duplicate"]
    W --> A["fast++"]
    X --> A
    A --> C
    C -->|No| E["Return slow + 1"]
  

Here the two pointers move at different times. fast scans every element, while slow only advances when the scan finds a new unique value. The section before slow always holds the deduplicated prefix, and the scan compares against nums[slow], the last value that was kept, rather than the raw neighbor at fast - 1. That detail is what makes the overwrite safe.

When to Use This Pattern

These problem characteristics point to two pointers.

  • The input is sorted, or you are allowed to sort it, and you need a pair that satisfies a condition such as a target sum. Opposite direction pointers find it in one pass and never re-check an index.
  • You need to check symmetry, like whether a string reads the same forwards and backwards. Comparing the two ends and moving inward is the whole algorithm.
  • The problem demands an in-place rewrite, for example removing duplicates or compacting kept values. Same direction pointers let one index scan while the other marks where the next kept value lands.
  • A brute-force solution would compare all pairs, and one comparison can rule out many pairs at once. That is the signature of this pattern. The wide-container and trapped-water problems both rely on this property.
  • The values fall into a small set of categories and need to be partitioned in place. Three pointers extend the same idea to three categories.

Complexity Analysis

VariationTimeSpaceNotes
Opposite direction
O(N)
O(1)
Each index visited at most once
Same direction
O(N)
O(1)
One full scan, one write position
Fixed pivot plus two pointers
O(N^2)
O(1)
N pivots, each running a linear pair search

The time is

O(N)
for the two base variants because the pointers only move toward each other, or both move forward, and neither ever moves backward. Each step advances at least one pointer, so the total number of steps is bounded by the array length. The fixed-pivot variant adds an outer loop, which is why 3Sum and its relatives cost
O(N^2)
after the initial sort.

The space is

O(1)
because the technique stores only the pointer indices. No hash map, no second array. The comparisons and writes all happen against the input itself.

Memorizing complexity tradeoffs is half the battle. The other half is remembering them under pressure. Review this pattern with spaced repetition so the analysis becomes automatic by interview day.

Common Mistakes

Recording a match without advancing. In pair-sum problems you return as soon as a match is found, so the loop always terminates. The bug appears when you generalize the code to a problem that collects multiple answers, like 3Sum. If you record a triplet and then move only one pointer, the loop re-checks the same pair forever. The mental model gap is treating “found a match” as the end of work instead of one step of it. To catch it during practice, always move both pointers past the matched pair, and trace 3Sum on an input that contains the same pair twice.

Using left <= right as the loop condition. With <=, the pointers eventually land on the same index. In a pair-sum problem, that single element can be reported as a valid pair when the target happens to equal twice its value. In a palindrome check, it compares a character with itself. The condition left < right is correct because it keeps the two indices distinct. Test your solution on an array of length 1 and length 2 with a target equal to twice the first element.

Forgetting the empty-input guard in same direction code. The dedup template starts with slow = 0 and returns slow + 1. On an empty array the loop body never runs, so the function returns 1 instead of 0, and the empty test case fails. The guard if (nums.length === 0) return 0 must be the first line of the function. The fix looks trivial, and that is exactly why it gets skipped under pressure.

Not skipping duplicate values in 3Sum. After recording a triplet, you must skip all equal neighbors on both sides before advancing the pointers. Without the skip, the same triplet is recorded again from a different pivot position, and the answer is rejected for containing duplicates. The skip is part of the algorithm, not an optimization. When practicing, run the solution against an input like [-1, -1, 0, 1, 2] and confirm each triplet appears exactly once.

These mistakes are expensive because they look correct until they fail on a hidden test case. Our review decks flag exactly these edge cases and drill them until they are reflex. Add this pattern to your queue .

Related Patterns

  • Sliding Window . Both patterns use two moving indices, but sliding window keeps a contiguous segment between them and changes it by one element per step. Use sliding window when a contiguous subarray must satisfy a running condition like a sum or a character frequency.
  • Array Manipulation . Reversal, rotation, and Dutch flag partitioning are all built on the same pointer mechanics. That page covers those in-place operations in depth, including the three-pointer case.
  • Hash Table . When the input cannot be sorted, two pointers lose their guarantees. A hash table finds complement pairs in
    O(N)
    time and
    O(N)
    space, and is the right tool when the input is unsorted or indices must be preserved.

Next Steps

Once the concept is clear, the next step is making the code automatic. Check out the code templates for memorizable implementations in 6 languages, then work through the practice problems to apply the pattern to real interview questions.

Reading about a pattern once is not enough to own it in an interview. Practice this pattern with spaced repetition and turn recognition into recall.