skip to content

What loop invariant makes the converging-pointer pair-sum search on a sorted array exact — why can an inward move never skip the answer?

level: middleimportance: must knowfreq 62%

answer

  1. state what stays true every iteration
  2. where must the answer live, if it exists?
  3. the discarded element's best partner fell short
  4. exchange argument: pair the low end with the max remaining
  5. empty window plus invariant equals proof of absence

basics

~20 s

The invariant: if any qualifying pair exists, at least one still lies inside the window between the pointers. Each move discards only pairs proven impossible — a too-small sum condemns every pair through the low element — so no answer is ever skipped.

solid answer

~40 s

The invariant is: every pair summing to the target, if any exists, has both indices inside the current window `[lo, hi]`. It holds initially — the window is the whole array. Each step preserves it by an exchange argument: when `a[lo] + a[hi] < target`, `a[hi]` is the largest element in the window, so `a[lo]` plus anything in the window is also below target — `a[lo]` belongs to no answer, and advancing `lo` discards only impossible pairs. Symmetrically, a too-large sum condemns every pair through `a[hi]`. So the scan is not a heuristic: if the loop ends at `lo == hi` without a match, the invariant proves no qualifying pair ever existed. At most n-1 moves, one pointer per non-matching comparison, O(n) total.

code

pseudocode · 11 lines
pseudocode
lo = 0
hi = length(a) - 1
while lo < hi
    s = a[lo] + a[hi]
    if s == target
        return (lo, hi)
    else if s < target
        lo = lo + 1
    else
        hi = hi - 1
return NOT_FOUND

go deeper

for a junior

Know the shape of the answer: something stays true every iteration, and that something guarantees the answer is never thrown away. Be able to say which pointer moves for a too-small versus too-large sum.

for a middle

An interviewer expects the full proof delivered cleanly: state the invariant, show initialization, argue maintenance via the max-remaining-partner exchange, and explain what termination without a match proves.

for a senior

Be ready to defend exactness under pressure — a skeptical interviewer will claim the scan needs a quadratic backstop. Refute it with the invariant, and show how the template adapts to closest-sum and predicate variants.

for a principal

Treat the invariant as the reusable asset: in design reviews, insist that any pointer-elimination scan ships with its stated invariant, because the proof obligation is what catches the subtle wrong-pointer move before production does.

## The claim to prove The converging-pointer pair-sum scan reports a pair summing to a target `T` in a sorted array, or reports absence, in one O(n) pass. Skeptics call it a heuristic — "how do you know an inward move never steps over the answer?" The reply is a standard **loop-invariant proof**, and being able to deliver it out loud is precisely what this question tests. ## The invariant *If any pair of indices `i < j` satisfies `a[i] + a[j] == T`, then at least one such pair satisfies `lo <= i < j <= hi`.* In words: **the window never loses the last remaining answer.** ## Proving it in three steps 1. **Initialization.** Before the first iteration, `lo = 0` and `hi = n - 1`: the window is the whole array, so every answer (if any) trivially lies inside. The invariant holds. 2. **Maintenance — the exchange-of-possibilities argument.** Suppose the invariant holds at the top of an iteration. - If `s = a[lo] + a[hi] < T`: sortedness makes `a[hi]` the maximum of the window, so for every `j` in the window, `a[lo] + a[j] <= a[lo] + a[hi] < T`. Every pair that includes index `lo` is thereby proven not to sum to `T`. Advancing `lo` removes exactly those pairs from consideration — all of them impossible — so any answer that was inside the window is still inside. - The `s > T` case mirrors it: `a[lo]` is the window minimum, so every pair including `hi` overshoots, and retreating `hi` discards only impossible pairs. - The invariant survives both moves. Note the discipline: **exactly one pointer moves per non-matching comparison.** Moving both on a miss would discard pairs no argument condemned. 3. **Termination.** Each iteration either returns or shrinks `hi - lo` by one, so the loop ends within `n - 1` iterations. If it ends with `lo == hi`, the window contains no pair at all — and the invariant says any existing answer would still be inside it. An empty window plus the invariant is a ***proof of absence***, not a shrug. That is the difference between an exact algorithm and a heuristic. ## Why "the sum approaches the target" is the wrong invariant A tempting but false claim is that `s` moves monotonically toward `T`. It need not: advancing `lo` across a large value gap can overshoot wildly, and the sum may oscillate around the target for many steps. Correctness does not rest on the sum's trajectory; it rests on what each comparison *proves* about the discarded element. Interviewers use this distinction to separate candidates who memorized the motion from candidates who understand the proof. ## One comparison kills up to n pairs, not one The scan makes at most `n - 1` comparisons yet adjudicates all Θ(n²) pairs. The resolution: each comparison eliminates an entire **pencil of pairs** — everything through the discarded index — in one stroke. Candidates who believe one test eliminates only the single tested pair conclude that a quadratic check is unavoidable; the exchange argument is exactly what frees you from it. ## Worked flavor In a refund-matching setting — transaction amounts sorted ascending, target chargeback `T` — the argument narrates naturally at a whiteboard: "this smallest amount, even paired with the biggest amount left, cannot reach the chargeback; no other partner is bigger, so this amount is dead — strike it." Each strike is justified the moment it happens, which is why the final "no match" is trustworthy. ## Generalization The proof template — a **window invariant** plus a **monotonicity fact** that condemns one end — extends beyond exact sums: - closest-sum tracking (record the best seen; the discarded element provably cannot beat it with any remaining partner); - mirror-constraint verification from both ends of an ordered roster; - any pairwise predicate monotone in each argument. Recognizing the template is worth more than the specific loop.

  • Can the same proof pattern find the pair closest to the target rather than an exact match?
    Yes — keep the elimination rule and additionally record the best sum seen so far. The exchange argument still shows the discarded element cannot beat the recorded candidate with any remaining partner, so the tracked best is optimal when the window closes. That is the general shape: any move that provably discards only dominated pairs preserves optimality.
  • Why does the loop run while lo < hi rather than lo <= hi?
    A pair needs two distinct positions; at lo == hi the only candidate would pair an element with itself, which is not a valid answer. Stopping there is also all the proof needs: a one-element window contains no pair, so the invariant turns an empty-handed exit into a proof that no qualifying pair exists.
  • Does the invariant argument still hold when the array contains duplicates?
    Yes — nothing in it assumes distinct values. When the sum is too small, every window element is still at most a[hi], so the low element remains partnerless regardless of ties. Duplicates only complicate the separate task of enumerating every distinct pair, which needs additional skip logic on top of the same invariant.

saying these in an interview costs you the question

  • Calls the technique a heuristic that might miss pairs on adversarial input
  • Claims the sum monotonically approaches the target each iteration
  • Believes one comparison eliminates only the single tested pair
  • Cannot state what an empty-handed loop exit actually proves

context