Skip to content

PATTERN Cited by 1 source

Invariant-driven programming

Pattern

Invariant-driven programming is the discipline of, for every non-trivial piece of code, explicitly naming the invariants the code relies on, writing them down (in comments, types, assertions, or tests), and checking each invariant against the substrate semantics rather than the intended semantics.

Substrate semantics = what the machine / language / runtime / protocol / hardware actually does. Intended semantics = what the programmer meant.

When the two diverge, bugs live in the gap. The invariant-driven programmer checks every invariant in the substrate's frame, not in their own mental frame.

The canonical instance: (low + high) / 2

From Bloch, 2006, quoting Jon Bentley's description of the binary-search midpoint line:

[this line] sets m to the average of l and u, truncated down to the nearest integer.

The stated invariant — "mid is the average of low and high, truncated down" — is:

  • True in the mathematical integers.
  • False in the 32-bit signed int machine representation, once low + high > INT_MAX.

The invariant was never checked against the substrate. Bentley proved it correct. Bloch implemented it in the JDK. Reviewers read it. Tests passed on small inputs. The bug survived for ~20 years in Programming Pearls and ~9 years in java.util.Arrays.binarySearch.

Bloch's opening paragraph is the statement of this pattern:

I remember vividly Jon Bentley's first Algorithms lecture at CMU… The key lesson was to carefully consider the invariants in your programs.

The irony — Bentley's lesson is the lesson; Bentley's code is the counterexample — is the wiki mnemonic for this pattern.

The discipline

For each piece of code you write, ask:

  1. What invariants does this rely on? Range of inputs, ordering, nullness, thread-ownership, monotonic sequence, freshness, referential integrity, allocation alignment, memory ordering, etc.
  2. For each invariant, what substrate is it true in? The integers? 32-bit signed? 64-bit unsigned? IEEE 754 doubles? A TCP ordered-delivery-within-a-connection? A single-writer WAL? Strong / weak / eventual consistency? A cache with TTL? An HTTP request where X-Forwarded-For is attacker-controlled?
  3. Is the substrate I actually have the substrate the invariant requires? If not — make them match (widen the type, switch algorithm, tighten the contract, add a guard) or write the check.

The four substrate-gap bug classes

Wiki taxonomy of substrate mismatches, all covered by invariant-driven discipline:

Techniques — how to operationalize

  • Type the invariant. If a value must be non-negative, use an unsigned type; if it must be sorted, use a sorted-collection type; if it must be non-null, use an Option / Maybe. The systems/aurora-dsql Rust-for-Postgres-extensions decision is an instance: encode the C-convention invariants into Rust types so violating them is uncompilable.
  • Assert the invariant. Write assert ! / invariant ! / debug_assert ! at every boundary where the invariant is entered or exited. Debug builds catch the violation loudly; property-based tests exercise the boundary.
  • Property-test the invariant (patterns/property-based-testing). Don't enumerate cases — describe the invariant as a universal property and let the test harness generate inputs that try to break it. Dropbox Nucleus is the canonical wiki instance.
  • Formal-model the invariant (concepts/lightweight-formal-verification). For durability-critical code, write an executable spec in the same repo + language; check the production implementation against it on every commit. ShardStore is the canonical wiki instance.
  • Document the invariant where the code lives. A comment above (low + high) / 2 saying "invariant: low + high fits in int" would have surfaced the bug at review time. Writing the invariant down is itself a debugging technique — half the bugs get fixed while writing the comment.

The epistemic consequence

From Bloch's closing paragraph:

Careful design is great. Testing is great. Formal methods are great. Code reviews are great. Static analysis is great. But none of these things alone are sufficient to eliminate bugs: They will always be with us.

Invariant-driven programming is not a silver bullet; it's a discipline that makes each of the other techniques more effective by giving them something concrete to check. Typed invariants make types catch more; asserted invariants make tests catch more; property-based invariants make generative testing cover more; formal-spec invariants make verification industrializable.

Seen in

Last updated · 200 distilled / 1,178 read