Algorithm correctness Created 2026-10-13 Updated 2026-10-18
Partial correctness means that, if the algorithm terminates on a permitted input, its output meets the formal specification. Total correctness additionally requires algorithm termination on every permitted input.
A precondition describes permitted starting circumstances; a postcondition describes the required result. Partial correctness says that starting with and finishing implies . Total correctness also guarantees finishing. A loop invariant supplies the connection between these conditions during repeated steps.[ref]
Loop invariants. CS 2112 Fall 2019; Loop invariants, Using loop invariants to show code is correct: Establishment, Preservation, Postcondition, and Termination
Correctness is relative to a formal specification. Returning the first entry of an array correctly implements "return the first entry", but fails "return the maximum" on . A fast wrong answer does not acquire algorithm correctness from its time complexity. Numerical closeness matters only when the formal specification allows an approximation. Software testing and test coverage provide evidence about selected executions; they do not by themselves establish algorithm correctness for every permitted input.
Binary array sum by recursion 2026-10-18
Keep an array unchanged, and sum entries in the half-open integer interval of array indices . The precondition requires integers and exact, terminating integer addition. An empty range returns the empty sum zero; a one-entry range returns that entry. Every longer range splits at its middle array index:For the original array , call . The left half returns , the right half returns , and the original invocation returns . The right half's left endpoint increases from zero to two: what decreases is range length , not every argument. Negative entries affect the answer but do not affect algorithm termination.
S(A, l, r):
require integer indices with 0 <= l <= r <= length(A)
size = r - l
if size == 0:
return 0
if size == 1:
return A[l]
middle = l + floor(size / 2)
left_sum = S(A, l, middle)
right_sum = S(A, middle, r)
return left_sum + right_sumSeven calls compute an array sum with only three frames active at once
. Original binary tree of invocations for , with integer intervals labeling ranges and upward arrows labeling returned sums. Small numbers give the order in which invocations begin when the left child runs first. The highlighted graph path contains the three active stack frames while the entry is read; the result from the completed left sibling is retained by its parent. Details.For range length , the children have lengths and , both positive and strictly less than . The floor function chooses the split; the ceiling function gives the other length. Algorithm termination follows by this natural number measure. For algorithm correctness, apply mathematical induction on : the two base cases return the specified sum; for a larger range, the two disjoint child ranges cover every original array index exactly once, so addition of their correct sums gives the required sum.
For , the invocation tree is a full binary tree with leaves, one per array entry. If is its number of nonleaf vertices, counting edges gives , so . Thus the total invocation count is , and there are combining additions. Here gives seven calls and three combining additions. For , there is one call and no combining addition; for , there is also one call and no combining addition, but no entry leaf, so the formula does not apply.
The call stack holds the active branch, rather than the entire invocation tree. Execute the left child completely before the right child, retaining the left result in the parent stack frame. The maximum number of active stack frames is for , and one for . Balanced splitting therefore needs frames for , compared with the frames of the factorial recursive process. Assuming constant-cost array access, index operations and addition, its time complexity is and its auxiliary space complexity is machine words for , excluding the stored input. Pass the same array with different endpoints, rather than copying slices. These bounds require all stored indices and partial sums to fit in a constant number of machine words; exact unbounded integer addition and storage require a separate bit cost analysis.
Factorial recursion and returning frames 2026-10-18
For a natural number , the factorial satisfies and for . The following description includes the zero base case explicitly; a negative or nonintegral input is outside its precondition:For , the arguments on descent are . Each positive-argument invocation postpones one multiplication until its recursive call returns. At the deepest point, the call stack has five stack frames: four suspended callers and the active zero base case. The invocation at zero returns to the invocation at one, rather than finishing the entire computation. Returns then giveA stack frame retains the caller's argument and where to resume. When the invocation at three receives , it uses its own , obtains , and returns that value to the invocation at four. The recursive calls share a definition, but their local variables belong to separate invocations.
F(n):
require n is an integer and n >= 0
if n == 0:
return 1
child_result = F(n - 1)
return n * child_resultThe natural number measure decreases by one on every recursive call, so algorithm termination follows. For algorithm correctness, use mathematical induction: the zero base case returns ; assuming the child's answer is , the caller returns . This also explains why keeping the caller's matters.
There are exactly invocations and multiplications, including one invocation and no multiplications when . Ordinary execution of this recursive process uses at most active stack frames, hence frames for in Big O notation. If each test, call, subtraction and multiplication is counted as constant cost, its time complexity is . That is an arithmetic-operation model: exact arbitrary-length integer multiplication has growing cost, and neither the factorial output nor every stack frame's numeric contents occupies a universally fixed number of bits.
Formal specification Created 2026-10-14 Updated 2026-10-18
A mathematically precise description of permitted behaviour. A formal specification for the microkernel file-read example's service could relate an authorized request to the exact returned bytes; a formal specification of its microkernel's IPC need only describe the protected transport operation. They state different obligations.
For the summation loop verification, require a nonnegative integer input and a returned value satisfying . The formal specification describes what answer is acceptable without requiring this particular loop: another implementation may satisfy the same contract. State separately whether partial correctness or total correctness is required. A Hoare triple packages the precondition, the program and the postcondition; Hoare logic supplies rules for establishing such claims.
Precision alone does not ensure a useful requirement. The postcondition permits returning zero for every nonnegative integer input; it does not specify the intended triangular number. Conversely, an impossible precondition permits no starts, making a universal correctness claim vacuous. Check both that intended inputs are admitted and that admitted outputs express the intended task.
Hoare triple 2026-10-18
A partial correctness claim connecting a precondition , a program and a postcondition : every terminating execution begun with ends with . State separately whether total correctness is intended.
Loop invariant Created 2026-10-13 Updated 2026-10-18
A statement true whenever execution reaches a chosen point in a loop, usually immediately before its condition is tested. Establish it initially, preserve it through each iteration, and combine it with the exit condition to establish algorithm correctness. Termination needs a separate argument.[ref]
Loop invariants. CS 2112 Fall 2019; Loop invariants, Using loop invariants to show code is correct: the three partial-correctness obligations and separate Termination step
Write for the loop invariant, for the condition, for the precondition, and for the postcondition. Prove that initialization under establishes , that a completed body starting with and restores , and that with false implies .[ref]
Loop invariants. CS 2112 Fall 2019; Loop invariants, Using loop invariants to show code is correct: Establishment, Preservation, and Postcondition
In the prefix maximum correctness example, the loop invariant remembers exactly the maximum of the processed prefix and the valid index bounds. The weaker statement survives every iteration, but does not imply the postcondition: for , the wrong value satisfies it. A useful loop invariant retains enough information to recover the answer when the condition becomes false.
Partial correctness 2026-10-18
Partial correctness means that every terminating execution begun in a program state satisfying the precondition finishes in a program state satisfying the postcondition . For a sequential program , the usual Hoare triple notation isHere the notation expresses partial correctness; it makes no promise that finishes. The precondition and postcondition describe permitted starting circumstances and required final circumstances, rather than tests performed automatically by the program.[ref]
An Axiomatic Basis for Computer Programming by Tony Hoare. Hoare, 1969, §3 Program Execution, p. 577, and §4 General Reservations, pp. 578–579; the paper instead labels the precondition P, program Q and result R
For a sharp example, take the precondition and the following sequential program over mathematical integers:The condition remains true, so execution never reaches the instruction after the loop. Consequently its partial correctness claim holds for the postcondition , and even for the impossible postcondition : there is no terminating execution with a wrong final result. This is vacuous truth, not evidence that the loop computes a sum. The same program fails total correctness for this permitted input.
i = 0
s = 0
while i < n:
i = iThe distinction is useful in a proof: first show that an answer, whenever produced, is right; then establish algorithm termination. The summation loop verification supplies both arguments for a loop that actually progresses. Its finite trace illustrates one execution, while its loop invariant covers every permitted input.
Postcondition 2026-10-18
The condition required of a final program state when a program finishes under its precondition. The summation loop verification requires returned value to satisfy and preserves the input . Relating the answer to the original input matters: allowing the program to replace by zero would change the intended contract.
A postcondition alone gives no promise of algorithm termination. It also need not specify a unique output: allows many answers, while the exact triangular number equation determines one integer answer for each permitted . Stronger postconditions express more obligations; proving only a weaker consequence cannot justify the stronger requirement.
Precondition 2026-10-18
The condition required of a starting program state for a stated correctness guarantee. In the summation loop verification, and admit and , but exclude . The accompanying model assumes exact integer arithmetic and terminating elementary operations. The initial values of need no restriction because initialization overwrites them before use.
A precondition is a logical assumption, not automatically an executable check. Writing
require in a procedure states its contract; an implementation that checks or rejects invalid inputs needs that behavior specified too. Weakening a precondition admits more starts and therefore demands more proof: dropping here makes a terminating counterexample to , since initialization returns zero while . Prefix maximum correctness example 2026-10-18
Given a nonempty finite sequence of integers, return its maximum value. Represent the sequence by an array whose contents remain unchanged during execution. The precondition requires an integer length and exact, terminating integer comparisons and variable assignments. The postcondition for returned value isThus the output belongs to the finite set of entries and is at least every entry. The contract permits ties and negative integers; it asks for a value, not the position of a particular occurrence. There is no maximum of an empty set of integers, so empty input is deliberately outside this precondition. A service accepting empty inputs needs a different formal specification, such as a separate absent result.
This Python implementation makes the variable assignments explicit. Its index arithmetic models exact integer arithmetic; fixed-width implementations additionally need enough index range for :For the original input , initialization gives and . Reading keeps ; reading changes it to ; reading and keeps . At successive condition checks, the pairs are . The returned maximum is .
def find_max(a):
n = len(a)
best = a[0]
k = 1
while k < n:
if a[k] > best:
best = a[k]
k = k + 1
return bestAt each condition check, use the loop invarianttogether with the fact that the input array is unchanged. The prefix is the first entries of the sequence, so its finite set of values is nonempty. The maximum need not be unique as an occurrence: the invariant concerns its value.
Initialization establishes : and , the maximum of a one-element set. For preservation, suppose and hold. Then is a valid array entry. Either the comparison replaces with the larger value , or it keeps a value at least . Before increasing , the resulting value isThe variable assignment restores the complete loop invariant. Between the two variable assignments, the old prefix formula may temporarily be false; the loop invariant is required at condition checks, not at every instruction.
At exit, , while gives . Hence , and gives exactly the required postcondition. This establishes partial correctness. The preservation argument is mathematical induction on the number of completed iterations, covering every permitted integer sequence, rather than just the pictured input.
For algorithm termination, the loop variant is a nonnegative integer whenever holds. If , then . Every body terminates under the precondition and changes to . Starting at , it reaches zero after exactly iterations. For , the initial value is already zero and the body is never entered. Together with partial correctness, this proves total correctness.
Starting at zero instead of fails on the original negative input : it returns zero, which is neither an entry nor the maximum . Keeping forever preserves the correct initial prefix claim but loops forever when ; a loop invariant alone does not ensure algorithm termination. Using
>= instead of > still returns the same maximum value when entries tie, although a version returning a position would need an explicit tie rule. Summation loop verification 2026-10-18
Compute , the triangular number , for a nonnegative integer . The empty sum gives zero when . Use exact mathematical integers and keep the input unchanged; every comparison and variable assignment is a terminating operation. The original sequential procedure isThe order of the two variable assignments matters: the addition uses the newly incremented . For , the pairs at successive condition checks are . At the last check, is false, and the returned sum is .
require n is an integer and n >= 0
i = 0
s = 0
while i < n:
i = i + 1
s = s + i
return sThe precondition is with , under the exact-arithmetic model just stated. The postcondition for returned value is , with still equal to its starting value. At each condition check use the loop invariantand the loop variant . The equation says that the accumulator is the triangular number for precisely the processed prefix ; the inequalities connect that prefix to the loop's condition.
The summation loop restores its invariant before each condition check
. Original sequential control flow for the summation loop verification. The blue loop head carries the loop invariant; the orange body increments before adding it to . A false condition leads to the postcondition. The loop variant decreases on the return arrow. All values are mathematical integers and remains unchanged. Details.Initialization gives , so holds, including when . For preservation, let the old values satisfy and . Write and for the values after both variable assignments. Since are integers, gives , andThus the completed body restores . Between the two variable assignments, the equation can be false; the loop invariant is required at the loop head. This preservation step is mathematical induction on the number of completed iterations.
At exit, the false condition gives , while gives . Hence , so : this proves partial correctness. For algorithm termination, gives the nonnegative integer . When the condition is true, ; the terminating body gives . Therefore the loop cannot repeat forever. Together these arguments prove total correctness for every permitted input, including .
Two changes expose what the proof checks. Swapping the two body instructions computes , returning rather than for . Leaving unchanged can preserve an initial equation forever without producing any answer. The first change breaks the intended postcondition; the second breaks algorithm termination. A trace can reveal these failures, while the two proof obligations explain them.
The exact-arithmetic assumption has a concrete boundary. In a stipulated unsigned eight-bit model where each addition wraps modulo , fits in the input representation, but its intended triangular number is . The stored accumulator instead finishes at , so the mathematical postcondition fails. This is integer overflow, and the proof above does not apply to that altered model. Restricting the same implementation to prevents overflow in its stored loop values, since ; alternatively use a representation supporting the required exact integer arithmetic. The equations in the proof are mathematical statements, not instructions to evaluate products in eight bits.
Total correctness 2026-10-18
Total correctness requires partial correctness and algorithm termination for every program state allowed by the precondition. For a deterministic sequential program, every permitted start must reach a final program state satisfying the postcondition after finitely many steps. Thus an infinite execution can satisfy partial correctness, but refutes total correctness for a permitted start.
In the summation loop verification, the loop invariant establishes the answer conditional on exit, and the loop variant establishes exit. Both parts matter: replacing the body by a step that never returns would defeat total correctness even if every completed iteration preserved the loop invariant. Likewise, a terminating loop with the wrong sum fails partial correctness and therefore fails total correctness.
These obligations depend on the formal specification. A program that rejects every input might correctly implement a rejection service, but does not meet a contract promising a sum for every nonnegative integer. A proof of total correctness also gives no particular physical response-time guarantee: time complexity and the execution environment require their own assumptions.
Well-founded progress in recursion 2026-10-18
A base case and a decreasing natural number measure work together. Check that every invocation satisfies its precondition, each base case finishes with the correct output, and every other invocation makes finitely many recursive calls with strictly smaller measure. All local work must also terminate. By well-founded induction, the children finish, so their caller finishes; if their answers are correct and the combination rule preserves the postcondition, the caller is correct too. This proves total correctness, not just correctness conditional on return.
Strict decrease without a lower bound is insufficient: subtracting one forever from a negative integer never reaches zero. Even a positive real-valued measure can decrease indefinitely, as shows. A well-founded relation rules out that descent. In structural recursion, a proper child subtree of a finite binary tree supplies progress without numeric arguments. The proof obligation is the relation between parent and child instances, together with terminating local work and complete base cases.




