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]
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.
Behavioral subtyping 2026-10-25
Substitution of an implementation for an interface while preserving its behavioral contracts, not just method types. Strengthening a method's precondition or weakening its postcondition can invalidate substitution. A nonnegative immutable refinement of a signed count value can inherit an exact subtraction returning the signed supertype; changing that same operation into truncated subtraction would not satisfy its parent's exact-difference postcondition.
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.
Formal verification Created 2026-10-13 Updated 2026-10-18
Establishing specified properties of a system using formal proofs about a precise model. Formal verification connects an implementation to a formal specification; correctness of the model and its assumptions still matters when applying the result to a physical system.
For a small example, the summation loop verification below starts with executable steps and asks two separate questions: does any returned value satisfy the postcondition, and does execution reach that return? A loop invariant answers the first; a loop variant answers the second. This turns an unbounded family of permitted inputs into a few reusable proof obligations.
The model must specify programming language semantics, including the meaning of variable assignments, comparisons and integer arithmetic. A proof about exact mathematical integers need not describe a machine that wraps on integer overflow. Similarly, a correct formal proof of a weak or mistaken formal specification does not establish the intended behavior. Choosing the right formal specification and relating the model to the implemented system remain substantive tasks.
Software testing examines chosen executions and can expose a counterexample. A formal proof instead justifies a statement throughout its stated model; it does not require executing every permitted input. This makes formal verification valuable when rare boundary cases matter, while software testing can still reveal an incorrect model or misunderstood requirement.
In 1969, Tony Hoare presented rules for variable assignment, composition and iteration in a small programming language. His paper explicitly says those rules do not prove successful termination, and discusses the dependence of arithmetic reasoning on the chosen overflow behavior. These reservations already make the model's scope part of the argument.[ref]
Hoare logic Created 2026-10-18 Updated 2026-11-05
Hoare logic proves properties of programs through Hoare triples . A precondition constrains initial states and a postcondition constrains returned states. The basic rules establish partial correctness; termination requires a further argument.[ref]
For a program expression whose evaluation is defined and has no side effects, the assignment rule is
The substitution replaces the free occurrences of in the desired postcondition. For example, obtaining after x := x+1 requires beforehand, so suffices. Integer overflow must be excluded or handled by the stated arithmetic model.
Sequential composition joins a first postcondition to the next precondition. For a while loop, proving establishes
Here is the loop invariant. For while x > 0 do x := x-1, starting with an exact nonnegative integer, is preserved and the false exit guard yields . The decreasing nonnegative integer is also a loop variant, establishing total correctness for this example.[ref]
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]
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]
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 Created 2026-10-18 Updated 2026-10-24
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 is
Here 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]
For a sharp example, take the precondition and the following sequential program over mathematical integers:
i = 0
s = 0
while i < n:
    i = i
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.
The 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.
Use an abstract finite sequence of integers, initially empty. In the following trait, comments are the mathematical specification; means the abstract state on entry, and result means the returned value.
trait ReversableBuffer {
  // Abstract state s: finite sequence of Int, initially [].

  // Pre: true. Post: s = s0 ++ [x].
  def append(x: Int): Unit

  // Pre: true. Post: s = [x] ++ s0.
  def prepend(x: Int): Unit

  // Pre: 0 <= i < length(s0).
  // Post: result = s0 !! i,
  //       s = take i s0 ++ drop (i+1) s0.
  def get(i: Int): Int

  // Pre: true. Post: s = reverse(s0).
  def rev: Unit
}
The postcondition of get includes removal: it is not merely indexed reading. In particular,
Repeated equal values elsewhere in the sequence remain present. List concatenation, indexing and list reversal in the specification describe abstract behavior without committing to a physical representation.
Let be a saved mathematical copy of the original array. A precise postcondition for sorting is
The precondition is that a is a non-null array of length . Its length and every unrelated object remain unchanged. The second clause preserves the array multiset, including all multiplicities; ordering alone would also permit overwriting the entire array with zeros. The saved is a specification device and need not be allocated by the implementation.
Use three-way quicksort. Fix the pivot value before moving any entries, and group equal entries together so that none needs a recursive call. These helpers and the sorter belong inside an enclosing scope containing a.
def swap(i: Int, j: Int): Unit = {
  val t = a(i); a(i) = a(j); a(j) = t
}

def split(l: Int, r: Int): (Int, Int) = {
  // Pre: 0 <= l < r <= a.length.
  val p = a(l)
  var lt = l
  var i = l
  var gt = r
  while (i < gt) {
    if (a(i) < p) {
      swap(lt, i)
      lt += 1
      i += 1
    } else if (a(i) > p) {
      gt -= 1
      swap(i, gt)
    } else {
      i += 1
    }
  }
  (lt, gt)
}

def quicksort(l: Int, r: Int): Unit = {
  require(0 <= l && l <= r && r <= a.length)
  if (r - l > 1) {
    val (lt, gt) = split(l, r)
    quicksort(l, lt)
    quicksort(gt, r)
  }
}

// Sort the whole array with quicksort(0, a.length).
The array partitioning loop invariant is
with unclassified. Swaps preserve the segment's array multiset and leave all entries outside it unchanged. The loop variant decreases in every branch. When swapping an entry from the right into i, do not increment i, since the incoming entry still needs classification. At termination,
Here the middle segment is nonempty because the original pivot occurs somewhere in the preserved array multiset. Both recursive segments therefore have length strictly smaller than . Mathematical induction on segment length proves that the two recursive calls sort their segments, and their ordering relative to the middle segment proves the complete sorting postcondition.
There is no additional array. The call stack and returned boundary pairs still use auxiliary space. An all-equal input takes one linear scan and no nontrivial recursive calls, so its time complexity is for . Balanced partitions give time complexity; a fixed first pivot can still give on adversarial distinct inputs. The worst-case call stack has entries.
For a proposed loop invariant , initial condition , guard , body , and postcondition , establish these Hoare logic obligations:
The first establishes before the loop; the second preserves it after each iteration; the third derives the desired result when the guard is false. Together they prove partial correctness. For total correctness, also prove algorithm termination, for example by a nonnegative integer loop variant that strictly decreases whenever the body executes. The loop invariant need not be the postcondition: it generally describes completed and unfinished work together.
Take the abstract state to be a finite sequence of integers, initially empty. Let denote the entry state, the empty sequence, and concatenation. A Scala trait presents the interface; its comments give the mathematical preconditions and postconditions:
trait DoubleEndedQueue {
  // Pre: true. Post: result == s0.isEmpty and s == s0.
  def isEmpty: Boolean
  // Pre: true. Post: s == [x] ++ s0.
  def addLeft(x: Int): Unit
  // Pre: s0.nonEmpty. Post: result == s0.head, s == s0.tail.
  def getLeft(): Int
  // Pre: true. Post: s == s0 ++ [x].
  def addRight(x: Int): Unit
  // Pre: s0.nonEmpty. Post: result == s0.last, s == s0.init.
  def getRight(): Int
}
The double-ended queue has no abstract capacity limit in this first specification. Both removals require nonemptiness; the query leaves the state unchanged, an instance of command-query separation.
A fixed-size array cannot implement unbounded successful insertion. Either specify a bounded double-ended queue with an extra not-full insertion precondition, or retain the unbounded interface and specify a possible full-capacity failure. Silently overwriting an endpoint would change the stated behavior. Here use the bounded contract, with a fixed positive capacity MAX and abstract state . Both additions require ; the other postconditions remain unchanged. The class declaration in part (c) is interpreted as implementing this explicitly revised contract, rather than as a behavioral subtype of the unrestricted contract in part (a).
The concrete state is an array a of length MAX, a first-position index head, and an occupied-entry count size. Its representation invariant is
The abstraction function is for . Unoccupied slots are unconstrained. Storing size distinguishes empty and full states even though their two circular endpoint positions coincide. MAX=1 is valid. Using a circular buffer avoids shifting entries and makes every endpoint operation constant time.
Place this object method inside the concrete class. Its precondition is the representation invariant; its postcondition is that the abstract data type state is unchanged and its left-to-right contents have been printed in bracket notation followed by a newline:
def display: Unit = {
  print("[")
  var i = 0
  while (i < size) {
    if (i > 0) print(", ")
    print(a(slot(i)))
    i += 1
  }
  println("]")
}
Assume ordinary sequential execution and successful output. The loop invariant is , unchanged representation, and output consisting of [ followed by precisely the first abstract values, separated by commas and spaces. The loop variant decreases. At exit the closing bracket completes the required display, including [] for emptiness. This takes output operations and constant space.
Return the new root of the affected subtree, assigning that result back to the parent's child reference:
def addToTree(word: String, t: Tree): Tree = {
  require(word != null)
  if (t == null) new Tree(word, 1)
  else {
    val c = word.compareTo(t.word)
    if (c < 0) t.left = addToTree(word, t.left)
    else if (c > 0) t.right = addToTree(word, t.right)
    else {
      require(t.count < Int.MaxValue)
      t.count += 1
    }
    t
  }
}
// Updating the whole structure uses: root = addToTree(word, root)
Assume the input binary search tree satisfies part (b)'s representation invariant and, if the key already occurs, its count can be incremented. Structural induction proves the postcondition. For an empty subtree, the fresh entry represents one occurrence. Otherwise the word comparison identifies the only possible child subtree containing the word. The recursive call adds exactly one occurrence there, preserving the inherited strict bounds; in the equal case only the count changes. The number of entries in the current subtree strictly decreases in each recursive call, proving algorithm termination. The returned binary search tree represents the old multiset plus one occurrence of word. With height , at most comparisons are made; without balancing this can be linear in the number of words. Actual comparison work also depends on the compared string data type lengths.
Replace suspended recursive calls by an explicit stack for an inorder traversal. Save each original right child before overwriting that link:
def flattenIterative(): (Tree, Tree) = {
  val pending = new scala.collection.mutable.Stack[Tree]
  var current = root
  var start: Tree = null
  var end: Tree = null
  while (current != null || pending.nonEmpty) {
    while (current != null) {
      pending.push(current)
      current = current.left
    }
    val t = pending.pop()
    val nextSubtree = t.right
    t.left = end
    if (end == null) start = t else end.right = t
    t.right = null
    end = t
    current = nextSubtree
  }
  root = null
  (start, end)
}
The method belongs to the concrete class owning root. Clearing root records that ownership has transferred to the returned doubly linked list; callers retain both returned endpoints. The inner loop invariant says that the current subtree lies before all its pending ancestors in inorder traversal; pushing and descending left preserves this fact. Its loop variant is the number of entries along the remaining left-child path, a finite decreasing quantity.
The outer loop invariant says that start through end is precisely the already emitted sorted prefix, with reciprocal links and null outside endpoints. current and the pending ancestors describe exactly the remaining traversal in order; original links that are still needed have been retained. A pending ancestor has not yet had its right child overwritten. The next popped entry is therefore the next sorted entry. Saving nextSubtree preserves its future work before assigning t.right = null; linking it to end extends the prefix by exactly one entry. The number of original entries not yet emitted is an outer loop variant, since each full outer iteration emits one. At exit nothing remains, proving the same preservation and ordering postconditions as the recursive version.
Each entry is pushed and popped once, giving time complexity and auxiliary space. No new Tree entries are allocated. The historical mutable stack interface provides the push and pop operations used here; its later deprecation does not change their meaning.[ref]
start == null if and only if end == null. Otherwise there is a finite list of distinct software objects , with start eq v0, end eq v(m-1), v0.left == null, and v(m-1).right == null. For every valid adjacent pair, v(i).right eq v(i+1) and v(i+1).left eq v(i). These are exactly the entries reachable through the list links, so there is no extra cycle or branch. All counts are positive, all words are non-null, and words strictly increase from start to end.
This is the representation invariant for a null-terminated doubly linked list without a list header. The abstraction function reads the ordered word-count pairs from left to right. Conversion additionally requires the output entries, counts and words to be precisely those of the input binary search tree; that preservation property is an operation postcondition, rather than an invariant of an arbitrary list by itself.
Use pattern matching and an optional value to distinguish absence from a present result:
class PartialFn[T, U](val data: List[(T, U)]) {
  def get(x: T): Option[U] =
    data.find { case (key, _) => key == x }.map(_._2)

  def remove(x: T): PartialFn[T, U] =
    new PartialFn[T, U](data.filterNot { case (key, _) => key == x })

  def add(x: T, y: U): PartialFn[T, U] =
    new PartialFn[T, U]((x, y) ::
      data.filterNot { case (key, _) => key == x })
}
The representation precondition is that equal keys always have equal associated values, with a stable equality relation on keys. This permits harmless repeated identical pairs; it excludes conflicting outputs for one input. get therefore returns Some(y) exactly when the represented partial function is defined at x. Removing filters out every occurrence of the key and leaves all other pairs unchanged. Adding first removes every old occurrence and prepends exactly the new pair, proving the partial function update postcondition even if the input contains duplicate identical pairs. The immutable linked list representation leaves the original mapping unchanged. Each operation has worst-case time complexity for pairs under constant-cost key equality.
The abstract state is a finite partial function from strings to strings, initially empty. Names and numbers below are non-null mathematical strings. store replaces an old binding if present; delete reports whether a binding existed.
trait Book {
  // Abstract state B: finite partial map String -> String; initially empty.

  // Pre: name != null && number != null.
  // Post: B' = B updated at name with number.
  def store(name: String, number: String): Unit

  // Pre: name != null && name is in dom(B).
  // Post: result = B(name), and B' = B.
  def recall(name: String): String

  // Pre: name != null.
  // Post: result iff name is in dom(B), and B' = B.
  def isInBook(name: String): Boolean

  // Pre: name != null.
  // Post: result iff name was in dom(B); B' = B with name removed.
  def delete(name: String): Boolean
}
These preconditions and postconditions specify an abstract data type, independently of any linked list. In particular, a failed deletion leaves the abstract state unchanged, and recalling a missing name is outside the operation's specified domain.