skip to content

Eiffel and Ada 2012 let a type declare an invariant that the runtime checks on your behalf, while Java, Python and C# leave you to re-check by hand in every mutator. For the languages that check automatically, at exactly which moments does the check fire — and why is it deliberately NOT evaluated during a call the object makes on itself?

level: middleimportance: should knowfreq 40%

answer

  1. invariant holds at rest, not at every instant
  2. Eiffel: qualified calls only, self-calls skipped
  3. Ada: Type_Invariant at package boundary; Dynamic_Predicate per assignment
  4. D invariant blocks stripped by -release
  5. hole: calling out while broken (observer, ctor virtual call)

basics

~20 s

An invariant constrains an object's stable states, not every instant. Eiffel evaluates the class invariant on entry to and exit from qualified calls (x.f); D runs invariant blocks around public member functions; Ada 2012 checks Type_Invariant when a value crosses the package's visible boundary. Internal self-calls are skipped so a routine can legally break and rebuild state.

solid answer

~60 s

The invariant is a predicate over quiescent states — the moments when the abstraction is handed back to a client. - **Eiffel**: checked on entry and exit of every exported routine invoked *with a target* (`x.f`). An unqualified self-call (`f`) skips the check, precisely so a routine may break the invariant midway while it rebuilds two fields that must agree. - **Ada 2012**: `Type_Invariant` is checked when a value of the type is returned from or passed into a subprogram declared in the visible part of its package; operations internal to the package may violate it freely. Ada also has `Dynamic_Predicate` on a subtype, checked on every assignment — a different granularity in the same language, and a useful contrast. - **D**: `invariant {}` blocks run before and after public member functions, never during, and disappear entirely under `-release`. - **Java, Python, C#**: no construct, so the predicate lives in prose plus a hand-written `checkRep()` you must remember to call. All four share one hole: calling *out* while broken — notifying an observer, or a constructor invoking an overridable method — lets someone observe an illegal state.

code

eiffel · 14 lines
eiffel
class ACCOUNT
feature
    deposit (amount: INTEGER)
        require amount > 0
        do
            balance := balance + amount
            update_history        -- unqualified: invariant NOT re-checked here
        end
feature {NONE}
    update_history do ... end
invariant
    balance_matches_history: balance = history_total
end
-- checked on entry/exit of a.deposit(x); not around update_history

go deeper

for a junior

Know what a class invariant is, that it must hold when a client can observe the object, and that every mutation should go through a method that re-establishes it.

for a middle

State the firing rule for at least one language precisely — Eiffel's qualified-call rule — and explain why self-calls are excluded rather than treating it as an oversight.

for a senior

Lead with the reentrancy hole: constructors calling overridable methods, observers fired mid-update, references published before construction ends, and the batching discipline that avoids them.

for a principal

Decide where the checking boundary of your system is, which predicates deserve a per-assignment constraint versus a boundary invariant, and what happens to each class of check in a production build.

## What a class invariant actually claims A class invariant is a predicate over an object's fields that must hold whenever the object is at rest — that is, whenever control is outside the object and a client could look. It is not a claim about every instruction. If a `Rectangle` must satisfy `width * height == area`, then a routine that sets width and then recomputes area has broken the invariant in between, and that is fine. The invariant is a statement about the abstraction's *stable* states, and the boundary of the abstraction is where it is enforced. This distinction is why the languages that automate the check all specify a precise firing rule, and all of them exclude the object's calls to itself. ## Eiffel: the qualified-call rule Eiffel writes the predicate once in an `invariant` clause at the end of the class. With assertion monitoring enabled, the runtime evaluates it on entry to and exit from every exported routine that was called *qualified* — with an explicit target, `account.deposit(x)`. A routine that calls another routine of the same object without a target (`update_totals`) does not re-trigger the check. The reason is structural. If the check fired on unqualified calls, no routine could ever decompose itself into helpers while state was mid-flight; you would have to write every multi-field update as a single monolithic body. Excluding self-calls says: while control is inside the object, the object is allowed to be inconsistent, because nobody outside can observe it. The invariant is thus conjoined implicitly to the precondition and postcondition of every exported operation. ## Ada 2012: crossing the package boundary, and a second granularity Ada 2012 attaches `Type_Invariant` to a private type. It is checked when a value of that type crosses the boundary of the package that defines it: on return from a visible subprogram, and on values arriving from outside. Inside the package body, operations may leave the value inconsistent for as long as they like. Ada then supplies a deliberately *different* tool: `Dynamic_Predicate` (and simple range constraints such as `subtype Percent is Integer range 0 .. 100`) are checked on every assignment and parameter passing, not at a module boundary. Having both in one language is instructive — a per-assignment check is right for a scalar constraint that has no intermediate states, while a boundary check is right for a multi-field consistency claim that must be temporarily broken to be maintained. ## D: contract blocks that vanish D gives a class an `invariant { ... }` block, run before and after every public member function and after the constructor. It also compiles the block out entirely under `-release`. This makes the contract explicitly a *bug detector for development*, not a production guard — a distinction that matters enormously when the same predicate also happens to be validating something that arrived from outside the program. ## Languages with no construct Java, C#, Python, TypeScript and most of the mainstream have no invariant clause. The equivalent discipline is: 1. make the fields private so every mutation goes through a method you control; 2. establish the invariant in the constructor and refuse to build an object that violates it; 3. write a private `checkRep()` or `assert invariant()` and call it at the end of every mutator. Step 3 is the fragile one. The predicate exists in one place, but the *calls* to it are duplicated at every exit point, so the failure mode is a mutator added six months later that forgets. Nothing detects the omission — which is precisely the duplication that Eiffel's declarative clause removes. ## The hole every one of them shares Because the check fires at the boundary, the danger is *calling out* while the invariant is broken: - A constructor that invokes an overridable method: the subclass override runs on a half-built object whose own fields are not yet initialised. - An observer notification, event fire, or callback issued between two field updates: the listener re-enters the object and sees the inconsistent state, and the boundary check has already been passed. - Publishing `this` — putting the object into a registry, a collection, or another thread — before the constructor finishes. No automatic checking rule closes this, because from the runtime's point of view the object legitimately made an unqualified call and control simply escaped. The defensive rules that follow are the same in every language: never call an overridable method from a constructor, never publish a reference before construction completes, and batch mutations so notifications happen only after the object is back in a legal state. ## Invariants versus pre- and postconditions Keep the three straight. A **precondition** constrains the state and arguments on entry to one operation. A **postcondition** relates the entry state to the exit state of that operation. An **invariant** is not attached to any single operation — it is implicitly conjoined to the pre- and postcondition of *all* exported operations of the type. That is exactly why a language can check it automatically at the boundary while it could never guess your preconditions. ## Answering well State the quiescent-state framing first, then give one language's rule precisely (Eiffel's qualified-call rule is the crispest), contrast a per-assignment granularity such as Ada's `Dynamic_Predicate`, and finish with the reentrancy hole, since that is the part that shows you have debugged a real system rather than read a manual.

  • Why is calling an overridable method from a constructor dangerous, and how does it relate to invariant checking?
    The subclass override executes while the object is only partially constructed: the base fields may be set but the subclass's own fields are still at their default values, so the object is in a state its own invariant forbids. No boundary check helps, because the call originated inside the object and the constructor has not yet returned to a client. The rule is to keep constructors free of overridable calls, or to move the work into a factory that fully builds the object before anything can dispatch on it.
  • When would you prefer a per-assignment check like Ada's Dynamic_Predicate over a boundary invariant?
    When the constraint concerns a single value with no legitimate intermediate state — a percentage between 0 and 100, a non-empty identifier, a bounded index. Such constraints can never be temporarily false during a correct operation, so checking every store catches the error at its origin rather than at the next boundary crossing. Multi-field consistency claims are the opposite: they must be temporarily broken to be re-established, so they belong at the boundary.

A shop's closing checklist. It must be true when the doors are open to customers, not while the staff are mid-restock with boxes in the aisle — but if you let a customer in through the back door during the restock, the checklist never protected them.

saying these in an interview costs you the question

  • Claiming the invariant must hold at every instruction, which would make any multi-field update impossible to write.
  • Believing a language's automatic invariant check protects against reentrancy — an observer callback or a constructor's virtual call slips past it.
  • Treating the invariant as something to check only in the constructor, so later mutators can quietly violate it.
  • Confusing an invariant with a precondition: a precondition belongs to one operation and constrains its arguments, an invariant is conjoined to all exported operations.
  • Assuming D's invariant blocks or Python asserts are present in production builds.

context