Loop Invariants

Overview

A loop invariant is a condition that is true immediately before and immediately after each iteration of a loop. (Note that this says nothing about its truth or falsity part way through an iteration.)

For example, in Java, a while loop has the following syntactic form, where B is a boolean expression (that we shall call the guard of the loop) and S is a sequence of commands/instructions (that we shall call the body of the loop).

while (B) { S }

A flow chart that indicates the steps taken in executing such a loop is as follows:

                        +-------+
                        | start |
                        +-------+
                            |
                            | <--- P (the precondition) holds here (assumption)
                            |
+-------------------------> * <--- I (the invariant) holds here (every time)
|                           |
|                           |
|                           v
|                          / \
|                         /   \
^                 true   /  B  \   false
|               +<------ \     / -------->+
|     I ∧ B --->|         \   /           |
|     holds     |          \ /            | <---- I ∧ ¬B  holds here
|     here      v                         |
|             +---+                       | <---- Q (postcondition)
|             | S |                       v       is to hold here
|             +---+                    +------+
|               |                      | stop |
|               |                      +------+
+<--------------+

Note: In what follows, we assume that evaluation of B has no "side effects", which is to say that it results in no changes to values of variables (including instance variables, which determine the state of an object) in the program. It is a good idea to follow this practice, even if the programming language does not enforce it (few do), in order to make it easier to reason about your programs. End of note.

The flow chart is annotated to indicate the "locations" at which

  1. we assume that the precondition P holds,
  2. we want the postcondition Q to hold, and
  3. we want the loop invariant I to hold.
It also indicates locations (or points in time, if you prefer) at which I∧¬B holds and at which I∧B holds, assuming that I holds every time execution reaches the "critical location" on the graph (i.e., the spot immediately above the diamond-shaped box indicating the evaluation of B). It is easy to choose an I that is an invariant of a loop, i.e., that always holds at the critical location. (For example, choosing I to be true works.) However, it is not always easy to choose an I that, in addition, is useful in demonstrating that Q must hold when the loop terminates. To be useful, the invariant I that we seek should be such that

I ∧ ¬B ⇒ Q

(i.e., the truth of I together with the falsity of the loop's guard, B, guarantees the truth of Q). Why? Because then we can be sure that, when the loop terminates (at which point I∧¬B must hold), Q, too, must hold, as desired. Later we will present heuristics (i.e., techniques/strategies that often work) that can be used in choosing loop invariants that not only satisfy I ∧ ¬B ⇒ Q but also that can help to guide the development of the loop.

How do you show that an invariant really is one?
A question that may have occurred to you is this: How do you show that a chosen condition I is actually true every time execution reaches the critical location? Usually, it is done as follows:

First, you show that P ⇒ I (i.e., the truth of the precondition guarantees the truth of I), from which you may conclude that I holds the first time execution reaches the critical location.

Note: Because almost every loop is immediately preceded by a small code segment (typically, one or two assignment statements) having as its purpose to initialize variables used in the loop, in practice what we usually do is to show that the initialization code, if executed beginning in a state in which P is true, will finish with the loop invariant I being true. Indeed, we can think of the pre-loop initialization code as having exactly that as its goal. End of note.

Second, you show that I holds each subsequent time the critical location is reached. To accomplish this, it suffices to show that, if S (the loop body) is executed beginning in a state in which I∧B holds, then I will necessarily hold when S is finished executing.

In effect, this amounts to a proof by mathematical induction on the number of loop iterations executed. The first step is the base case, which shows that I holds the first time execution reaches the critical location (i.e., after zero iterations). The second step is the inductive step, in which it is shown that, if, for any positive number n, I holds the n-th time execution reaches the critical spot (i.e., after exactly n-1 iterations), then it must also hold the (n+1)-st time (i.e., after exactly n iterations).

How do you show that a loop terminates?

To show that a loop must eventually terminate (i.e., after finitely many iterations), we show that each iteration makes progress towards termination in some way. Technically, this amounts to showing that there is some integer function of the program variables, called a bound function (or a variant), that

  1. decreases on each iteration and
  2. is such that the function's value must be greater than zero in order for another loop iteration to occur (or, equivalently, the truth of I∧B guarantees that the function's value is greater than zero).

Note that, at the beginning of each loop iteration, the value of a bound function is an upper bound on the number of iterations remaining to be executed.

For example, take the following loop:

while (i < n) { i = i+1; }

Then, taking g to be the function defined by the expression n-i, we have that the value of g decreases on each iteration (due to the fact that i increases on each iteration but n doesn't change). We also have that the truth of the loop guard, i<n, guarantees that, if any more iterations are to occur, the value of g exceeds zero.


Now for some concrete examples.

Computing Factorials

Recall that, for n satisfying n>0,

n! = 1 × 2 × ... × n

Suppose that we wish to develop a program that, given as input a positive integer n, assigns to r the value n!. That is, we are to develop a code segment having as its pre- and post-conditions P and Q, respectively, where

P: n>0   and   Q: r = n!

It seems clear that some kind of iteration is needed. In this situation, using the following heuristics often helps in the development of a loop that solves the problem:

Step 1: Form a modified postcondition Q' by replacing a constant (or a variable) in Q by a fresh variable and adding as a new conjunct that the two are equal.

In our example, taking n to be the constant and k to be the fresh variable, we get

Q' : r = k!  ∧  k = n

Note that n, although a variable, is constant in the sense that it is an input value not subject to being changed by our program.

Step 2: Choose the guard of the loop to be the negation of the "new" conjunct in Q' while taking the rest of Q' to be (the core of) the loop invariant I.

We get the following program "skeleton":

// P : n>0
< initialization of k and n to "truthify" I >
// loop invariant I: r = k!
while (k ≠ n)  {
   // I
   < loop body that preserves truth of I while making progress towards termination >
   // I
}
// Q': r = k!  ∧  k = n
// Q:  r = n!

Descriptions of code that needs to be inserted are nested between angle brackets. Wherever there is a comment that states a condition (e.g., P, I, Q, Q') it serves as an assertion that, whenever execution reaches that position in the code, that condition is true. In particular, the precondition P is (assumed to be) true initially, the postcondition Q is true at the end of execution, and the loop invariant I is true just before the loop is reached and just before and just after each time the loop body is executed.

Our assertion that Q' is true upon termination of the loop follows from the fact that it is simply the conjunction of I (which will be true after every loop iteration, including the last) and the negation of the loop guard (which must be true upon termination of the loop —indeed, it was precisely the falseness of the loop guard that caused the loop to terminate in the first place!). As for Q, note that it follows from Q', as was pointed out when Q' was first introduced.

It remains for us to supply the two code segments that will serve as the initialization and the loop body, respectively.

As for the initialization code, we must assign values to k and r so as to truthify (i.e., make true) I : r = k!. There are, of course, an infinity of possible choices. For example, 5! = 120, so we could do this:

k = 5; r = 120

This doesn't seem like a good choice, however. Indeed, it seems rather arbitrary.

So what might be a better choice? Well, if we notice that the precondition is n>0 and that the loop guard is k≠n, what they suggest is that we initialize k to the smallest possible value of n (1) and that we confer upon the loop body the responsibility to make k increase until it is equal to n.

If we are going to initialize k to 1, we must —in order to truthify I— initialize r to 1!, which is 1. Hence, we propose that the initialization code be

k = 1; r = 1;

Now we turn to the loop body. Its purpose is to make progress towards termination while at the same time preserving the truth of the invariant I : r=k!. The obvious way to make progress toward termination is to increment k so that the distance between its value and n (which is just n-k, of course) decreases by one. (Once that distance hits zero, the loop will terminate, after all.) This reasoning also tells us that a good choice for a bound function is n-k, as this expression satisfies the two properties that we desire, namely that its value decreases on each iteration and that its value cannot go below zero before the loop guard becomes false. Just to make the second point explicit, we should augment our loop invariant I to include as a new conjunct 0<k≤n, so that we now have

I : r = k! ∧ 0<k≤n

Our program skeleton has been refined to the following:

// P : n>0
k = 1;  r = 1;
// loop invariant I: r = k! ∧ 0<k≤n
// bound function: n-k
while (k ≠ n)  {
   // I
   k = k+1;
   r = ??;
   // I
}
// Q': r = k!  ∧  k = n
// Q:  r = n!

We have chosen to place the assignment to r (inside the loop body) after that to k, which was somewhat arbitrary. But what should the right-hand side of that assignment statement be?

Consider that, by incrementing k in a state in which I (and therefore r = k!) is true, we enter a state in which r = (k-1)! is true! If that confuses you, consider as an example the state in which k = 4 and r = 24 = 4! = k!. If, in that state, we increment k, we enter a state in which k = 5 and r = 24 = 4! = (5-1)! = (k-1)!

So what we need to determine is what value to assign to r in order to go from a state in which r = (k-1)! to a state in which I is true, and, specifically, r = k!. Well, what is the relationship between k! and (k-1)!? That's simple: k! = k × (k-1)!. Which means that the value we want to assign to r is k multiplied by r's "current" value, (k-1)!. Hence, an appropriate assignment is r = k * r. Our completed program is then:

// P : n>0
k = 1;  r = 1;
// loop invariant I: r = k! ∧ 0<k≤n
// bound function: n-k
while (k ≠ n)  {
   // I
   k = k+1;
   // r = (k-1)!
   r = k * r;
   // I
}
// Q': r = k!  ∧  k = n
// Q:  r = n!

Suppose that we had chosen, in developing the loop body, to place the assignment to k after that to r, rather than before. Then the annotated loop body would have looked like this:

// I
r = (k+1) * r;
// r = (k+1)!
k = k+1;
// I


Finding a Maximum Element in an Array

Suppose we have an array a[] (where N = a.length > 0) of, say, integers, and we want to determine the maximum among the values in a[]. Using maxVal as the output variable, the desired postcondition is

Q : maxVal == Max({a[0], a[1], ... , a[N)})

For brevity, henceforth we shall abbreviate the array segment <a[0], a[1], ..., a[k]> by a[0..k].) Using the approach described above, we replace constant N in the postcondition by fresh variable k in order to obtain a modified postcondition

Q' : maxVal == Max(a[0..k)) ∧ k = N

Choosing the first conjunct of Q' as the candidate loop invariant and the negation of the second conjunct as the loop guard, we derive this program skeleton:

// precondition P: N > 0  (where N = a.length)
< initialization code to establish I >
// loop invariant I: maxVal == Max(a[0..k))
while ( k != N )  {
   < loop body >
}
// Q' : maxVal == Max(a[0..k)) ∧ k == N
// Q  : maxVal == Max(a[0..N))

For initialization, we choose k = 1; maxVal = a[0];. To verify that this establishes I, plug in 1 for k and a[0] for maxVal in I and we get

a[0] = Max(a[0..0])

which is obviously true. (If a set has only one value, clearly that value is the set's maximum!)

We initialized k to one rather than to zero because, otherwise, we would have had to initialize maxVal to Max(a[0..0)), which is undefined. (An empty collection of numbers has no maximum!) This also explains why the precondition is N>0 rather than N≥0.

It remains to fill in the loop body, which must update k and maxVal so as to preserve the truth of I while also making progress towards termination. Given that we initialized k to 1 and that the loop terminates only when k = N becomes true, the obvious way to modify k is to increment it, thereby reducing the distance N - k between k and N.

Let K be the value of k at the beginning of a given loop iteration. Then, according to the loop invariant I, we have (at the beginning of that iteration) that maxVal = Max(a[0..K)). Because we are going to increase k's value by one (thereby making its value K+1) during the iteration (as proposed in the preceding paragraph), we must, in order to preserve the truth of I, ensure that maxVal's value at the end of the iteration is max(a[0..K]).

So we consider the relationship between max(a[0..K)) and max(a[0..K]). Clearly, that relationship is that max(a[0..K]) is the larger of max(a[0..K)) and a[K]. Choosing to update k after updating maxVal in the loop body (rather than before), we propose the following as our loop body:

// I : maxVal = max(a[0..k))  (and k=K)
maxVal = larger of Max(a[0..K)) and a[K]
// maxVal = Max(a[0..K])
k = k+1;
// I 

Of course, this is only pseudo-code, because K is not a program variable, Max(a[0..K)) is not a legal expression (even putting aside that it includes an occurrence of K), and "larger of" is not a legal operator.

But we can refine this pseudo-code into actual code as follows:

Because we are assuming that k=K holds as the loop body begins execution, any occurrence of K appearing before the assignment to k can be replaced by k without changing the effect. Since every occurrence of K comes before that assignment, we can replace all of them. (Had there been any occurrences of K after the assignment to k, we would have been justifed in replacing each one by k-1 because, at that point in execution, k-1 = K would be true.)

In particular, the assignment to maxVal becomes

maxVal = larger of Max(a[0..k)) and a[k]

But the loop invariant tells us that maxVal = Max(a[0..k)) is true as the loop body begins execution. Hence, we can replace Max(a[0..k)) by maxVal, which yields the assignment

maxVal = larger of maxVal and a[k]

This can be refined to either

maxVal = Math.max(maxVal, a[k])

or

if (maxVal ≥ a[k])
   { maxVal = maxval; }
else
   { maxVal = a[k]; }

To include an assignment of the form x = x would be an example of horrendous programming, as it has no effect. Hence, we can translate the above into a one-branch if-statement:

if (maxVal < a[k]) { 
   maxVal = a[k];
} 

Adopting this as our assignment to maxVal, the entire program is as follows:

// precondition P: N > 0  (where N = a.length)
k = 1; maxVal = a[0];
// loop invariant I: maxVal == Max(a[0..k))
while ( k != N )  {
   // I
   if (maxVal < a[k]) { 
      maxVal = a[k];
   }
   // maxVal = Max(a[0..k])
   k = k+1;
   // I
}
// Q' : maxVal == Max(a[0..k)) ∧ k == N
// Q  : maxVal == Max(a[0..N))


2-Color Partitioning Problem

Given an array a[0..N) (again, N abbreviates a.length) each of whose elements can be classified as either RED or BLUE, assign to k the number of occurrences of RED values in a[] and rearrange a[]'s elements (via swapping, but no other operation) so that all RED values end up in a[0..k) and all BLUE values end up in a[k..N)

In picture form, the desired postcondition Q is

   0                 k                N
  +-----------------+----------------+
a |     all RED     |    all BLUE    |   ∧ 0≤k≤N
  +-----------------+----------------+ 
That is, Q: every element in a[0..k) is RED ∧ every element in a[k..N) is BLUE

It is fairly obvious that any solution will involve a loop. In order to obtain candidates for the loop's invariant and guard, we rewrite postcondition Q as the slightly stronger

Q' : every element in a[0..k) is RED ∧ every element in a[k..m) is BLUE ∧ m = N

Note that Q' was obtained from Q by applying the replace a constant by a fresh variable heuristic; here, we replaced N by m in the second conjunct and added m = N as a new third conjunct. It should be clear to the reader that any state satisfying Q' also satisfies Q.

Following our usual approach, as a loop guard we propose using m != N, which is the negation of the third conjunct of Q'. As a loop invariant I, we propose using the rest of Q' (i.e., its first two conjuncts). That gives us

I: every element in a[0..k) is RED ∧ every element in a[k..m) is BLUE

which, as a picture, looks like this:

   0            k             m           N
  +------------+-------------+-----------+
a |  all RED   |  all BLUE   |     ?     |   ∧ 0≤k≤m≤N
  +------------+-------------+-----------+ 

Note: The series of inequalities 0≤k≤m≤N was introduced in order to make explicit the required relationships between 0, k, m, and N. Note that any values of k and m that do not satisfy these inequalities wouldn't really make sense. End of note.

Notice how these choices for I (the loop invariant) and B (the loop guard) automatically give us

I ∧ ¬B ⇒ Q'

which is vital in showing that, when the loop terminates, the postcondition holds.

Notice also that I says nothing about the contents of array segment a[m..N), which is why we label it in the picture by ? (which is supposed to indicate that we are asserting nothing about the colors of the elements there).

Given the above, we sketch our solution as follows:

k = ?;  m = ?;  // initialize k and m to truthify I

// loop invariant I : 
//    Every element in a[0..k) is RED ∧ every element in a[k..m) is BLUE 

while ( m != N )  {

   < code to preserve I while also making progress toward termination >

}
// Q': I ∧ m = N (from which Q follows)

Let us consider to which values our variables k and m should be initialized. Recall that the loop invariant requires that all elements in the segment a[0..k) to be RED and that all elements in a[k..m) to be BLUE. As we have no way of knowing —until after we've examined it— what color any particular array element might have, the only way that we can ensure that every element in these two segments is of the right color is to give values to k and m making those segments empty!! (Note that all values occurring in an empty array segment are RED. And all of them are BLUE, too!! In general, if D is any property at all, and a[p..q) is an empty segment (because p ≥ q), then the statement "Every element in a[p..q) has property D" is true!)

To make a[0..k) empty, we set k to zero. Given that choice, in order to make a[k..m) empty we must set m to zero as well. (The result, of course, is that the RED and BLUE segments are empty and the ? segment covers the entire array!)

Our still rather sketchy solution has been refined to this:

k = 0;  m = 0;  // initialize k and m to truthify I

// loop invariant I : 
//    Every element in a[0..k) is RED ∧ every element in a[k..m) is BLUE }

while ( m != N )  {

   < code to preserve I while also making progress toward termination >

}
// Q: I ∧ m=N (from which Q follows)

At this point we observe that, since m is initialized to zero and must finish with value N (in order to falsify the loop guard, thereby causing the loop to terminate), it would seem that a reasonable way to achieve the goal of "making progress toward termination" on each iteration would be for the body of the loop to cause m's value to increase. Indeed, this would correspond to a decrease in the length of the ?-segment, which has length N - m. (And when the loop guard becomes false (i.e., m = N becomes true), that corresponds to the length of the ?-segment becoming zero, at which point the postcondition has been established!)

It would seem sensible that the loop body should examine one of the elements in the ?-segment and, according to its color, take action necessary to place that value into the appropriate segment (either RED or BLUE). Suppose that, among the elements in the ?-segment, we choose to examine the one in the lowest-numbered location, which is a[m]. (An exercise below explores the possibility of making a different choice.)

So our loop body has this logic:

if (isRed(a[m])) {
   place a[m] into RED segment
}
else { // isBlue(a[m]) 
   place a[m] into BLUE segment
}

The case of a[m] being BLUE is the simpler, in that to "absorb" that element into the BLUE segment requires nothing other than to increment m! To reason through this in a more technical fashion, we can argue as follows:

Let M be the value of m at the beginning of the loop iteration in question. Then the loop invariant tells us that every elements in a[0..k) is RED and every element in a[k..M) is BLUE. Because a[m] is BLUE, too (that is our assumption) and m=M, it follows that all elements in a[k..M] are BLUE. If we then increase m by one, we arrive in a state in which m-1 = M and so all elements in a[k..m) are BLUE, which is consistent with the loop invariant. Because k was not modified, in the new state it is still the case that all elements in a[0..k) are RED. Hence, the invariant holds after incrementing m.

How about the case in which a[m] is RED? In order to place that element into the RED segment, we can swap it with a[k], which is where the BLUE segment begins. In effect, this shifts the BLUE segment "to the right" by one position (from a[K..M) to a[K+1..M], where K and M are the values of k and m at the beginning of this iteration) and it extends the RED segment one place "to the right" (from a[0..K) to a[0..K]. But this means that, after the swap, if we increment both k and m, we end up in a state in which k-1=K and m-1=M so that every element in a[0..k) is RED and every element in a[k..m) is BLUE, which is consistent with the invariant!

Assuming the existence of a method swap() that swaps the elements at the two specified locations of the specified array, a suitable loop body is thus

if (isRed(a[m])) {
   swap(a,k,m); k = k+1; m = m+1;
}
else { // isBlue(a[m]) 
   m = m+1;
}

If we "factor out" the assignment to m appearing at the end of each branch of the if-statement, we get

if (isRed(a[m])) {
   swap(a,k,m); k = k+1; 
}
else { // isBlue(a[m]) 
}
m = m+1;

Since the else-branch is "empty" we can simplify further to this:

if (isRed(a[m])) {
   swap(a,k,m); k = k+1; 
}
m = m+1;

The full solution is then

k = 0; m = 0;
// invariant I : every element in a[0..k) is RED ∧ every element in a[k..m) is BLUE 
while ( m != N ) {
   if (isRed(a[m])) {
      swap(a,k,m); k = k+1; 
   }
   m = m+1;
}
// I ∧ m=N (i.e., Q')
// Q (follows from Q')

 


Exercise 0: Modify the given solution to the 2-Color Partitioning Problem in such a way that the loop invariant remains as it is but so that the element of a[] examined during each loop iteration is the one at location N-1 (rather than m). You should find that every loop iteration involves at least one swap, and some iterations involve two swaps. (Be careful about the order in which the two swaps are carried out.)

Exercise 1: Develop an alternative solution to the 2-Color Partitioning problem based upon obtaining a candidate loop invariant and loop guard by replacing the second occurrence of k in the postcondition by fresh variable m, rather than by replacing N. We obtain k != m as a candidate loop guard and this as the candidate loop invariant:

   0             k         m              N
  +-------------+---------+--------------+
a |   all RED   |    ?    |   all BLUE   |   ∧ 0≤k≤m≤N
  +-------------+---------+--------------+
Exercise 2: Repeat the previous exercise, but this time replace 0 by fresh variable m. We obtain m!=0 as a candidate loop guard and this as a candidate loop invariant:

   0           m             k              N
  +-----------+-------------+--------------+
a |     ?     |   all RED   |   all BLUE   |    ∧ 0≤m≤k≤N
  +-----------+-------------+--------------+ 

Exercise 3: The 3-Color Partitioning Problem generalizes the 2-color problem by adding a third color, WHITE. Here is a graphical depiction of the postcondition:

   0             k               m              N
  +-------------+---------------+--------------+
a |   all RED   |   all WHITE   |   all BLUE   |    ∧ 0≤k≤m≤N
  +-------------+---------------+--------------+

In text, the diagram translates into:

Q: every element in a[0..k) is RED ∧ every element in a[k..m) is WHITE ∧ every element in a[m..N) is BLUE

(a) Develop a solution based upon obtaining a loop guard and loop invariant by replacing N by fresh variable r.

(b) Develop a solution based upon obtaining a loop guard and loop invariant by replacing the second occurrence of m by fresh variable r.

(c) Develop a solution based upon obtaining a loop guard and loop invariant by some replacement other than those indicated in (a) or (b).