core29 min studyLesson 231 in Mathematics

Loop invariants and termination

Turn an iterative procedure into a proof with preserved assertions, exit conditions, and a well-founded progress measure.

On this page

What you’ll understand

  • Separate partial correctness from guaranteed termination.
  • Prove a lower-bound search using a half-open interval invariant.
  • Find a nonnegative integer variant that decreases on every iteration.

A program can pass a thousand tests while mishandling an empty array or repeating forever on two elements. A correctness proof addresses every permitted input by describing what remains true as the machine changes state. The useful assertion usually expresses unfinished work rather than a completed answer. Pair that assertion with a quantity that cannot decrease indefinitely, and a loop becomes a finite inductive argument instead of a collection of plausible updates.

Specify the boundary before choosing the update

Consider a sorted array of length n and a target t. We want its lower-bound index: the first position whose value is at least t, allowing n when every value is smaller. This specification handles missing values and duplicates without exceptional meanings. Maintain indices l and r with the unresolved positions in the half-open interval [l,r). Positions before l are known to be too small; positions from r onward are known to be large enough. The indices satisfy zero ≤ l ≤ r ≤ n.

Initially l is zero and r is n. Both known regions are empty, so their universal claims hold vacuously. When l is smaller than r, choose the midpoint m by rounding their average downward. If the value there is smaller than t, sortedness certifies that every position through m is too small, so set l to m plus one. Otherwise every position from m onward is large enough, so set r to m. Neither update discards an unresolved candidate without a reason.

I:0≤l≤r≤n,(∀i<l)Ai<t,(∀i≥r,i<n)Ai≥tI:\quad 0\le l\le r\le n,\quad (\forall i<l) A_i<t,\quad (\forall i\ge r, i<n) A_i\ge t
The two classified regions surround the unresolved half-open interval. Array indices in the first quantifier are also understood to be nonnegative.↔ Scroll wide equations horizontally.

An invariant proves an implication at exit

Initialization proves the assertion before the first iteration. Preservation assumes it before an arbitrary iteration and derives it afterward. These steps are ordinary induction on the iteration number. At exit the guard l < r is false, while the invariant still gives l ≤ r, hence l equals r. The unresolved region is empty. Its shared boundary is exactly the first index that can satisfy the target comparison, including the legal answer n.

This establishes partial correctness: if the loop exits normally, the returned answer meets the specification. It does not establish that an exit occurs. The deliberately broken update l = m in the smaller-value branch preserves the classification but can stall when r = l + 1. An invariant that merely says the answer has not been discarded cannot detect that failure. Correctness arguments need both a description of the remaining candidates and an explicit explanation of progress.

V=r−l∈Z≥0,l<r⟹0≤Vnew<VV=r-l\in\mathbb Z_{\ge0},\qquad l<r\Longrightarrow 0\le V_{\mathrm{new}}<V
A strictly decreasing nonnegative integer cannot support infinitely many iterations. Both midpoint updates decrease this interval length.↔ Scroll wide equations horizontally.

Make the mathematical machine match the real one

A termination variant must take values in a well-founded order. Positive real values are insufficient: repeatedly halving one gives an infinite strictly decreasing sequence. Here integer interval lengths make the proof work. Each iteration takes constant time in a random-access comparison model with array entries and indices fitting machine words. The interval roughly halves, giving logarithmically many iterations. This resource bound is a separate theorem from correctness and should identify the operations being counted.

Implementation details can invalidate an otherwise sound proof. In a bounded integer type, adding l and r may overflow even when each index is valid. Computing l plus the integer half of r minus l avoids that sum under suitable nonnegative index bounds. Concurrent mutation can destroy sortedness during the search. An expensive comparison changes the time model. Writing assumptions beside the invariant makes these failure modes visible and tells a reviewer which parts belong to mathematics and which belong to the programming environment.

Find a boundary through duplicates

Trace lower-bound search for t = 4 in the sorted array [1,4,4,7,9], using zero-based indices and the half-open interval.

  1. Start with l = 0 and r = 5. The midpoint is 2 and its value is 4, so set r = 2. The new unresolved indices are 0 and 1; index 2 and everything after it are certified large enough.

  2. Now m = 1. Its value is again 4, so set r = 1. Then m = 0 has value 1, so set l = 1. The interval lengths have fallen from 5 to 2 to 1 to 0.

  3. At l = r = 1 the loop exits. Index zero has value smaller than 4, while index one and every later position have values at least 4. The invariant supplies the whole boundary claim, not just one equality test.

The result

The answer is index 1, the first of the repeated 4s. Searching for 10 would instead return 5, a valid boundary index outside the array's element positions.

Explore the model

Interactive lab

Interactive lab · modular

A preserved property can coexist with an endless cycle

Pause and reset, then set both controls to modulus 5 and additive step 2. Predict that all states remain valid residues but the orbit returns to zero after five steps; use Step to check. For a fresh comparison, pause and reset, then set modulus 5 and step 0: the state stays zero forever. Residue membership is an invariant in both runs, but neither supplies a strictly decreasing nonnegative variant. This finite cyclic model illustrates the logical distinction; it does not execute binary search.

A preserved property can coexist with an endless cycleAddition modulo m starts at zero: rₙ = na mod m. Playback advances two additions per second. The orbit has period m/gcd(m,a), including period one for a = 0. This is not a cryptographic security test or a primality algorithm. Current readings: Residue r0: 0; Orbit period · m/gcd(m,a): 12; Distinct residues visited: 1 of 12. All parameters are adjustable with the labeled controls below.Repeated addition modulo 12: the current residue is 0, and the orbit period is 12.01234567891011+ 5mod 12An additive orbitn = 0rₙ = 0Period = 12Cycle starts at 0:0 → 5 → 10 → 3 →8 → 1 → 6 → 11 →4 → 9 → 2 → 7 → 0Exact residues · two additions per second
Additive orbitCurrent residue / next edge
Residue r0
0
Orbit period · m/gcd(m,a)
12
Distinct residues visited
1 of 12
t = 0 s

Addition modulo m starts at zero: rₙ = na mod m. Playback advances two additions per second. The orbit has period m/gcd(m,a), including period one for a = 0. This is not a cryptographic security test or a primality algorithm. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.

Look forRemaining inside a valid state set proves neither progress nor termination.

Check your understanding

Which added claim would prove termination of a loop whose invariant is already preserved?

Try it yourself

For an array of length zero, prove that the lower-bound search returns the specified answer without accessing an element. Then explain why replacing the update l = m + 1 by l = m can fail on the one-element array [2] with target 3.

Take a moment to make a prediction or work it out on paper. Open a hint when you need one.

Give me a hint

Evaluate the guard immediately for the empty input. For the other input, calculate m before and after the proposed update.

Compare with the solution

For n = 0, initialization gives l = r = 0. The guard is false, both classified-region assertions are vacuous, and the returned index zero is the required boundary. No midpoint or array read occurs. With [2] and target 3, l = 0, r = 1, and m = 0. The broken smaller-value update leaves l unchanged at zero. The next state repeats exactly, so the loop never exits although it has not discarded any possible boundary. The correct update sets l to one and reduces the variant to zero.

Take it with you

  • A loop invariant connects initialization, each iteration, and the exit specification.
  • Total correctness also needs a well-founded progress argument.
  • Index conventions, integer overflow, and comparison costs are part of the algorithm's assumptions.

Read further

Original sources and deeper explanations to follow at your own pace.

Optional progress, saved in this browser when available. Revisit any lesson at any time.

Lesson not marked complete.