Dijkstra’s guarded if…fi as Gries develops it: a nondeterministic choice among guarded commands whose weakest precondition makes case-coverage a proof obligation, not a courtesy.
Student edition · understand, apply, verify
Understand guard coverage and correctness for every enabled nondeterministic branch.
What you should be able to do: Derive a selection without treating overlap as an error or a missing guard as harmless skip.
Assumes basic programming, not formal-methods notation. This guide defines the needed notation locally. Scalar proofs use mathematical integers unless stated otherwise; finite arrays require integer valid bounds. Guarded-command notation is not Python syntax. Machine overflow, floats, exceptions, aliasing, and concurrency need additional contracts.
Every conditional you have ever written picks a branch. Dijkstra’s alternative command — the guarded if…fi — does the same, but with two deliberate design choices that a conventional if/else hides from you. First, when several conditions are true it chooses any of them (nondeterminism). Second, when none is true it does not silently fall through — it aborts. Both choices exist to serve the same master the rest of Gries’ book serves: a clean weakest-precondition semantics that turns “did I handle every case?” into a formula you can actually check. This page covers the syntax, the wp rule, why coverage is a proof obligation, how to derive an IF from a specification, and the pitfalls.
This is an original study summary for quick reference. Notation follows Gries and Dijkstra: wp for weakest precondition, {P} S {Q} for a Hoare triple, and the guarded alternative if B1 → S1 [] … [] Bn → Sn fi. See The Science of Programming for the full, rigorous treatment.
Read these definitions first, or use the linked terms as you go. The examples and limitations are local to this guide; companion pages are optional.
Guarded commands and nondeterminism
Guarded selection can choose any enabled branch. Every enabled choice must preserve correctness. No enabled selection guard aborts; an iteration stops when all guards are false.
Example. Choosing max(x,y) with guards x≥y and y≥x permits either branch on equality.
Boundary. This differs from Python's first-matching if/elif. Check coverage under the precondition/invariant; do not assume fairness unless specified.
Weakest preconditions
wp(S,Q) is the least restrictive entry predicate guaranteeing S terminates normally and establishes Q in this guide's model. Derive it backward from the result.
Example. For x:=x+3 and Q:x>10, wp is x>7. For division, also require the denominator nonzero.
Boundary. Weakest liberal precondition (wlp) omits termination. Undefined expressions need definedness checks, not just textual substitution.
Predicates and state
A predicate is a true/false statement about values; a state assigns values to variables. A state satisfies P when P evaluates true in it.
Example. P:x≥0 holds in state x=3, not x=−1. 'Array sorted' is a predicate about a sequence.
Boundary. Specify integer/real/array domains. Division and indexing must be defined before reasoning about truth.
Preconditions and postconditions
A precondition states required inputs/state before an operation. A postcondition states the result promised when those requirements hold and execution terminates normally. Together they make requirements testable.
Example. For sum_first(b,n), require integer 0≤n≤len(b) and numeric elements; promise the sum of b[0:n].
Boundary. State exceptions, mutation, numeric domains, and empty cases. A mathematical array range does not automatically validate a runtime input.
Dijkstra’s guarded commands
The alternative command is a list of guarded commands separated by the fat bar [] and bracketed by if and fi. Each guarded command is a pair Bi → Si: a boolean guardBi and the statementSi that runs when it is chosen.
if B1 → S1
[] B2 → S2
[] …
[] Bn → Sn
fi
The operational semantics has three steps:
Evaluate all guards. Compute B1, …, Bn in the current state.
Choose one true guard. If at least one guard is true, pick one of the true guards and execute its statement. When more than one guard is true the choice is made nondeterministically — the language does not promise which arm runs, and a correct program must not care.
Abort if none holds. If no guard is true, the command aborts (equivalent to abort, whose wp is F). It does not quietly do nothing.
Why nondeterminism is a feature, not a bug
Beginners read “the machine may pick either branch” as a liability. It is the opposite. Nondeterminism lets you avoid over-specifying: when two cases genuinely lead to an equally correct result, forcing an arbitrary tie-break (as a sequential if/else does — the first clause always wins) bakes an accident of ordering into the program’s meaning. A guarded IF with overlapping guards says exactly what is true: “in the overlap, either arm is acceptable.”
It exposes symmetry. The max-of-two below uses x ≥ y and y ≥ x — visibly symmetric. A >/else version breaks the symmetry for no reason and hides that either choice works at x = y.
It keeps proofs honest. Because the implementation may choose any true guard, your correctness argument must hold for every true guard. You cannot lean on an execution order the semantics never promised.
It leaves room for the implementer. A compiler or scheduler is free to pick whichever true arm is cheapest, since all true arms are declared correct.
Weakest precondition of IF
Write IF for the whole alternative command. Its weakest precondition for a postcondition R is:
The rule splits cleanly into two parts, and each carries a distinct obligation:
First conjunct — coverage.(B1 ∨ … ∨ Bn) demands that at least one guard is true in the starting state. If it can be false, execution might reach a state where the command aborts, so wp (which encodes total correctness) cannot hold. This single term is what turns “handle every case” into mathematics.
Remaining conjuncts — correctness of each branch. Each (Bi ⇒ wp(Si, R)) says: whenever guard Bi could be the one selected, its statement Si must be guaranteed to establish R. Because selection among true guards is nondeterministic, every true guard’s branch must independently work — hence a separate conjunct for each.
Contrast with an ordinary if-then-else
In most languages, if B then S with no else is sugar for if B then S else skip. When B is false, the construct silently does nothing — a skip, whose wp is just R. That is permissive: a forgotten case slides by as a no-op and the bug surfaces far downstream. The guarded IF makes the opposite choice: a missing case is an abort, whose wp is F, which immediately poisons the whole wp. Stricter, yes — and safer, because the proof fails at the point of the omission instead of hiding it.
✗ Conventional if — missing else is a silent skip
# if B then S == if B then S else skip# wp = (B => wp(S,R)) ∧ (¬B => R)# when B is false: nothing happens, R# must already hold. A forgotten case# passes through unnoticed.
✓ Guarded IF — missing case is an abort
if B → S fi# wp = B ∧ (B => wp(S,R))# = B ∧ wp(S,R)# if B can be false, coverage fails# and wp collapses to F. The proof# forces you to state every case.
Coverage is a proof obligation
The first conjunct is not decoration — it is the term most designs get wrong. Consider the maximum of two values. The natural first draft uses strict inequalities, and it has a hole exactly at equality.
✗ Guards leave a gap at x = y
# goal R: m = max(x, y)if x > y → m := x
[] x < y → m := y
fi# coverage: (x>y) ∨ (x<y) == x ≠ y# at x = y neither guard holds → ABORT# first conjunct is NOT T, so wp fails
✓ Overlapping guards cover x = y
# goal R: m = max(x, y)if x ≥ y → m := x
[] y ≥ x → m := y
fi# coverage: (x≥y) ∨ (y≥x) == T ✓# at x = y BOTH guards hold — overlap.# either arm sets m to the common value,# so both establish R. Nondeterminism# is harmless here — by design.
Two lessons sit inside this small example:
Overlapping guards are allowed and often desirable. The wp rule never asks the guards to be mutually exclusive — only to cover. Where two guards overlap, the extra conjuncts simply require both arms to establish R, which the equality case here satisfies trivially.
Coverage and correctness are checked separately.(x ≥ y) ∨ (y ≥ x) reduces to T, discharging coverage; then x ≥ y ⇒ wp(m:=x, R) and y ≥ x ⇒ wp(m:=y, R) discharge the arms. The proof splits exactly the way the design should.
Deriving an IF from a specification
The wp rule is not just a checker — run it backward and it becomes a constructor. To build an IF that establishes postcondition R from precondition Q:
Choose guards that cover Q. Pick B1, …, Bn so that Q ⇒ (B1 ∨ … ∨ Bn). This guarantees the coverage conjunct under any state allowed by the precondition — no abort is reachable.
Design each branch to establish R under its guard. For every i, build Si so that the triple {Q ∧ Bi} Si {R} holds — equivalently Q ∧ Bi ⇒ wp(Si, R). The guard hands each branch an extra assumption Bi, which is usually exactly what makes the branch easy to write.
Notice how the labor divides: coverage is a property of the guard set as a whole, correctness is a property of each arm in isolation. You never reason about interactions between branches.
Worked example 1 — maximum of two
Establish R: m = max(x, y) from Q: T (works for all states).
# Step 1 — guards must cover Q = T.# pick x ≥ y and y ≥ x; their# disjunction is T, so coverage holds.# Step 2 — design each arm:# {x ≥ y} m := x {m = max(x,y)} ✓# {y ≥ x} m := y {m = max(x,y)} ✓if x ≥ y → m := x
[] y ≥ x → m := y
fi# {m = max(x, y)}
Worked example 2 — the “if a < b swap” style
A classic use of a single-armed IF paired with a skip arm: order two variables so that afterward a ≥ b. Here you must supply the second guard explicitly — the guarded IF gives you no implicit else, so the “do nothing” case is written out as a guard whose statement is skip.
# R: a ≥ b (values of {a,b} unchanged as a set)# coverage: (a < b) ∨ (a ≥ b) == T ✓if a < b → a, b := b, a # swap into order[] a ≥ b → skip # already orderedfi# {a ≥ b}# NOTE: omitting the a ≥ b arm would abort# whenever a is already ≥ b — the opposite# of a conventional guardless "if a < b then swap".
This is the deterministic special case: the two guards a < b and a ≥ b are exactly B and ¬B, so they partition the state space, there is no overlap, and the IF behaves like an ordinary if-then-else — except that the “else” is stated, not assumed.
Worked example 3 — sign of a number (n > 2 guards)
Guarded IF is not limited to two arms. To classify the sign of a into s ∈ {-1, 0, +1}, three guards partition the domain. This shows the rule generalizing to any n.
# R: (a < 0 ∧ s = -1)# ∨ (a = 0 ∧ s = 0)# ∨ (a > 0 ∧ s = +1)# coverage: (a<0) ∨ (a=0) ∨ (a>0) == T ✓if a < 0 → s := -1
[] a = 0 → s := 0
[] a > 0 → s := +1
fi# {s = sign(a)}
The three guards are mutually exclusive and exhaustive, so the disjunction is T (coverage ✓) and each arm trivially establishes its slice of R. If you had written only a < 0 and a > 0, coverage would reduce to a ≠ 0 and the command would abort at a = 0 — the proof would refuse to close, pointing straight at the omission.
Common patterns & pitfalls
Pattern / pitfall
What happens
Guidance
Uncovered case
An uncovered state is excluded from wp; the whole predicate need not be identically false at every other state. Check Q⇒guard coverage and correctness of every enabled branch.
Always verify Q ⇒ (B1 ∨ … ∨ Bn). Add a skip arm for the intended “do nothing” case.
Deterministic if-else as a special case
Two guards B and ¬B partition the state; no overlap, exactly one arm ever fires.
This is the familiar if-then-else. Perfectly fine — just remember the ¬B arm must be written explicitly.
Overlapping guards for symmetry
Both arms are declared correct in the overlap; the machine may pick either.
Use it deliberately (e.g. x ≥ y / y ≥ x) to expose symmetry and avoid an arbitrary tie-break. Ensure both arms establish R.
Side effects in guards
Guards are evaluated (all of them) before selection; a side-effecting guard would mutate state unpredictably and break the wp reasoning, which assumes pure boolean tests.
Guards must be side-effect-free boolean expressions. Move any effect into the statement Si, never the guard.
The side-effect rule deserves emphasis: the entire wp derivation treats Bi as a predicate over the starting state that can be evaluated freely and in any order. If evaluating a guard could change the state, “evaluate all guards, then choose” is no longer well defined, and none of the conjuncts in the wp rule mean what they say. Keep guards pure.
Total correctness note
wp encodes total correctness — guaranteed termination in a state satisfying R — so it is worth asking whether the alternative command can fail to terminate. It cannot, on its own account. Given that some guard holds (coverage), the IF evaluates the finite list of guards, selects one true arm, and runs its statement exactly once. It introduces no repetition. So the IF itself always terminates, provided the chosen branch Si terminates.
A selection adds no repetition itself, but an enabled branch can abort, raise an unmodeled error, or fail to terminate. Total correctness requires finite defined guards, coverage, and total correctness of every enabled branch.
Worked example: maximum of equal values
Work through the state changes and the exact claim being checked.
Sequence for this example. The table explains the evidence and decision at each step.
Step
Evidence, decision, and expected result
Try strict guards
For x=y=3, neither x>y nor y>x holds; guarded selection aborts at this state.
Use covering guards
x≥y and y≥x both hold at equality.
Prove both branches
Either m:=x or m:=y returns 3=max(x,y). Overlap is harmless because both satisfy the contract.
Keep the distinction
Ordinary if/elif picks its first match; guarded IF permits any enabled branch. Termination also requires the chosen branch to terminate.
Check your understanding: Does one uncovered state make wp false at every state?
No. It excludes that uncovered state. The predicate is not identically false unless coverage/branch correctness fails everywhere.
Run and change the example
Download guide_labs.py. Python 3.10+, standard library only. In its directory, run:
python3 guide_labs.py guards
python3 guide_labs.py test
The complete standard-library lab checks the stated finite cases and boundary inputs. Its invariants explain the reasoning; tests alone do not prove all possible inputs.
The program tests its stated finite contracts. It is an educational model, not an implementation of the named production product or a proof of unmodeled distributed faults.
if B1 → S1 [] … [] Bn → Sn fi — a list of guarded commands, fat-bar separated.
Selection semantics
Pick any one true guard’s statement; nondeterministic when several are true.
No true guard
The command aborts — not a silent skip.
Nondeterminism
A feature: avoids over-specifying, exposes symmetry, keeps proofs honest.
wp(IF, R)
(B1 ∨ … ∨ Bn) ∧ ⋀i (Bi ⇒ wp(Si, R)).
Coverage conjunct
An uncovered state is excluded from wp; the whole predicate need not be identically false at every other state. Check Q⇒guard coverage and correctness of every enabled branch.
vs. ordinary if-else
A missing case is an abort, not a skip — stricter and safer.
Overlapping guards
Allowed and often desirable; each overlapping arm must still establish R.
Deriving an IF
Choose guards so Q ⇒ (B1 ∨ … ∨ Bn); design each Si for {Q ∧ Bi} Si {R}.
Guards are pure
Side-effect-free boolean expressions only — effects go in the statements.
Termination
IF always terminates if the chosen branch does; the loop is where termination gets interesting.
The recurring theme: the guard set is a specification. Coverage and per-branch correctness are exactly the two things the wp rule checks, so writing a guarded IF is writing down what must be true before you choose — and letting the calculus confirm you left no case behind. Related: weakest preconditions, the assignment axiom & commands, and the loop (DO).