On a sorted array, two indices replace nested pair enumeration with a converging scan. left starts at the smallest value and right at the largest. Each sum comparison decides which index moves inward and rules out every pair anchored at the discarded index.

Order is the proof. Raising left can only increase the sum. Lowering right can only decrease it. One comparison can therefore settle a whole set of pairs. Without that monotonic relationship, moving either pointer is only a guess.

Visualization

The trace runs the converging pair-sum over the sorted array [1, 4, 5, 7, 9, 12, 15], searching for two elements that add to 14.

The first comparison is arr[0] + arr[6] = 1 + 15 = 16 > 14, so right moves left. The next comparison is arr[0] + arr[5] = 1 + 12 = 13 < 14, so left moves right. Each decision retains the compared indices before the chosen pointer moves, and the scan finishes at arr[2] + arr[4] = 5 + 9 = 14 ✓.

Sorted order is the invariant that turns each move into a proof instead of a guess. Consider the full grid of candidate pairs (i, j) with i < j: a brute-force loop inspects each cell. The converging pointers instead sit at one cell (left, right) and let a single comparison eliminate an entire line of that grid.

  • a[left] + a[right] < target: every pair (left, j) with j ≤ right uses a partner no larger than a[right], so all of them are even smaller. The column at left holds no solution; left++.
  • a[left] + a[right] > target: every pair (i, right) with i ≥ left uses a value no smaller than a[left], so all of them are even larger. The row at right holds no solution; right--.

Each move retires one index permanently. Neither pointer reverses direction or revisits a discarded pair.

Complexity
n
number of elements in the sorted input sequence

The chart describes the converging pass over already-sorted input. If sorting is required first, account for that preprocessing separately.

When the Proof Stops Working

The discard argument requires monotonic order. On the unsorted array [13, 1, 10, 4, 8, 3, 6], a search for 18 starts at 13 + 6 = 19 and retreats right. Every later sum is too small, so left keeps advancing. The scan reports no match even though 10 + 8 = 18 is present. Unsorted input fails quietly with a plausible false negative.

Duplicates change the output contract. Returning the first match needs no special handling. Enumerating distinct value pairs does: after a match, move left past equal values and move right past its equal run. On [2, 2, 3, 3] with target 5, plain left++; right-- reports (2, 3) twice. Skipping equal runs reports it once.

This pattern moves pointers toward each other from opposite ends. Fast and Slow Pointers sends both in the same direction at different speeds to detect a cycle or find a midpoint. That method does not depend on sorted order. Its invariant is relative position rather than a converging sum.

Diagram and C# Implementation

Comparison

StrategyRequiresStronger caseWeaker case
Converging two pointersSorted / monotonic inputOrder already exists and space is tightUnsorted or non-monotonic data
Brute-force nested loopsNothingTiny arrays where setup cost dominatesAny large input
Hash lookupNothingUnsorted input. Repeated complement lookupsMemory pressure. Ordered or range access
Binary Search per elementSorted inputComplements drawn from a separate sorted setPlain two-sum, where the converging pass already applies
Sliding WindowContiguous-subarray aggregateRunning sum or count over a windowComparing or rearranging arbitrary element pairs

Binary-searching each element’s complement repeats a separate search for every element. It makes sense when the operands come from different sorted sequences, not for plain two-sum. Sliding Window also moves indices forward, but it maintains an aggregate over a contiguous range rather than comparing two ends.

References