Concepts / Loop Invariants and Class Invariants

Loop Invariants and Class Invariants

Both source methods use a while loop that visits each array index once, controlled by a counter i compared against A.length.

  • Programming

A Loop That Looks Correct

A loop is easy to write and surprisingly hard to get right. Most loop bugs are not visible in the loop header. They hide in the way a few variables change from one pass to the next. This article works through two small array-processing methods written with while loops. Each one is short enough to trace by hand, which makes them good practice for a skill that matters throughout programming: stating what the loop has done so far and checking that it still matches what you expect.

What do you think happens?

Before reading on, predict the value a sum loop returns for the array [3, 7, 2]. Which variables would you need to track to be sure?

Reveal answer
Answer:

12

The loop adds 3, then 7, then 2. Tracking total and i after each pass shows the running sum reaching 12 once every index has been visited.

Total, Index, and the Loop Condition

The first method computes the sum of every element in an integer array. It uses two variables besides the array itself. The variable total holds the running sum, and the variable i is the index of the element being processed. The loop continues as long as i is less than the array length, so every valid index is visited exactly once.

The sum starts total at 0. Starting from 0 is the identity for addition: adding elements to 0 produces exactly the sum of those elements. Each pass through the body adds one element to total, and the index then advances to the next position. Because the condition is checked before every pass, the loop stops on its own once i reaches the length of the array.

Starti=0, total=0After pass 1i=1, total=3After pass 2i=2, total=10After pass 3i=3, total=12Loop exitsi=3 is not less than 3add A[0], advance iadd A[1], advance iadd A[2], advance icondition false
On each pass, what are the current values of i and total, and what changes before the next condition check?

Read the diagram as a sequence of states. Every arrow is one pass through the body, and every box is the record of what the loop has done up to that moment. The idea of recording state between passes is what the rest of this article depends on.

Tracing the Sum Loop by Hand

Hand tracing means recording the value of every variable before each check of the loop condition, then applying the loop body once per pass. The table of states is the record of what the loop has actually done. The worked example below uses a three-element array, [3, 7, 2], so you can follow the procedure on concrete numbers.

Worked example

Tracing arraySum on [3, 7, 2]

Trace the sum loop on the array [3, 7, 2], recording total and i before every condition check.

  1. 1
    Check 0: i = 0

    Initial state: total = 0, i = 0. The condition 0 < 3 is true, so a pass runs. The addition uses A[0] = 3, giving total = 3. Then i becomes 1.

  2. 2
    Check 1: i = 1

    Using the value of i left by the previous pass, the condition 1 < 3 is true. The addition uses A[1] = 7, giving total = 10. Then i becomes 2.

  3. 3
    Check 2: i = 2

    The condition 2 < 3 is true. The addition uses A[2] = 2, giving total = 12. Then i becomes 3.

  4. 4
    Check 3: i = 3

    The condition 3 < 3 is false, so no pass runs. The loop ends, and the return statement gives total = 12.

Result

The sum loop returns 12, which equals 3 + 7 + 2.

Checki before checkCondition i < 3total after passi after pass
10true31
21true102
32true123
43false12 (no pass)3

State record for the sum loop on [3, 7, 2]. The final check is false, so it is not a pass.

Two details in the trace are worth checking every time. First, the condition is evaluated before each pass, including the check that would begin a pass at index 3. That check is false, which is why the loop stops and does not run a fourth pass. Second, each check must use the value of i from the end of the previous pass, never a value remembered from earlier. In the table, the i before check column always copies the i after pass column from the row above it.

Why the Loop Body Order Matters

Inside the sum loop body there are three statements. The addition uses A[i] while i still refers to the current element. Only afterward does i = i + 1 move the index forward. The order is part of the logic, not a stylistic choice. Order inside a loop body is one of the most common places where a result diverges from expectation.

Before
i = i + 1
total = total + A[i]

The index moves first, so the addition reads the element after the one intended. Every later step is shifted by one position.

After
total = total + A[i]
i = i + 1

The addition reads the current element, and only then does the index advance. Each element is added once.

Worked example

What the swapped order does to [3, 7, 2]

Trace the swapped body from the previous block on the array [3, 7, 2] and compare the result with 12.

  1. 1
    Check 0: i = 0

    The condition 0 < 3 is true. The index moves first, so i = 1. The addition then reads A[1] = 7, giving total = 7. The element 3 is never read.

  2. 2
    Check 1: i = 1

    The index moves to 2 first. The addition reads A[2] = 2, giving total = 9.

  3. 3
    Check 2: i = 2

    The index moves to 3 first. Reading A[3] would be outside the array, so the loop would need the condition to stop before reaching this point. The check 3 < 3 is false, so the loop exits with total = 9.

Result

The swapped version returns 9 instead of 12. The first element is skipped, and the divergence began at the first pass.

Where the Return Statement Goes

The return statement in the sum method sits after the loop, not inside it. The method therefore returns only once the condition i < A.length has become false, which means every index has been processed. Returning from inside the loop would stop the traversal after the first element.

Notes on the code
  • The loop header checks i < A.length before every pass
  • The addition uses the current index before it advances
  • The return sits after the loop, so it runs only when every index has been processed

Consider what happens if the return is moved into the body. On the array [3, 7, 2], the first pass would add 3 and then return 3. The method would look like it works on that first element, but it would silently ignore the other two. This kind of bug is easy to miss because the code runs without error.

Starting Values for the Maximum Loop

The second method finds the largest value in an array. It keeps a candidate for the maximum, called max, and compares each remaining element against it. Whenever an element is larger than the current max, that element becomes the new max. When the traversal ends, max holds the largest value seen.

This version differs from the sum loop in its starting values. The method initializes max to A[0], the first element, and starts the index at 1 so that the first element is not compared with itself. The starting value of max must be an element that is actually in the array, because the loop only ever replaces max with an element it has seen.

Worked example

Tracing the maximum loop on [3, 7, 2]

Trace the maximum loop on [3, 7, 2], starting with max = A[0] and i = 1.

  1. 1
    Start: max = 3, i = 1

    The condition 1 < 3 is true. The element A[1] = 7 is larger than max = 3, so max becomes 7. Then i becomes 2.

  2. 2
    Check: i = 2

    The condition 2 < 3 is true. A[2] = 2 is not larger than max = 7, so max stays 7. Then i becomes 3.

  3. 3
    Check: i = 3

    The condition 3 < 3 is false, so the loop ends and returns max = 7.

Result

The maximum loop returns 7, the largest element of [3, 7, 2].

Mistakes Beginners Make with Loop State

  • Common mistake

    Starting max at 0 for an array that may contain only negative numbers

    What it looks like

    For the array [-5, -2, -9], a max loop that starts at 0 never changes max and returns 0.

    Why it fails

    The loop can only ever select an element it compares, and 0 is not in the array.

    Do this instead

    Start max at A[0] and begin the index at 1, so the candidate is always a real element.

  • Common mistake

    Incrementing the index before using it in the body

    What it looks like

    Swapping i = i + 1 and total = total + A[i] makes [3, 7, 2] return 9 instead of 12.

    Why it fails

    The addition reads the element after the intended one, so every later step is shifted.

    Do this instead

    Use A[i] first, then advance i at the end of the body.

  • Common mistake

    Returning from inside the loop

    What it looks like

    Placing return total inside the body of the sum loop on [3, 7, 2] returns 3.

    Why it fails

    The method stops after the first pass, so the other elements are never processed.

    Do this instead

    Place the return statement after the loop, so it runs only when the condition is false.

  • Common mistake

    Using a remembered value of i instead of the value left by the previous pass when tracing by hand

Practice Tracing a New Array

Use the hand-tracing procedure on an array you have not seen yet. Before each condition check, write down total or max, i, and the condition result. Then answer the questions below.

MEDIUM

Trace the maximum loop on the array [-4, -1, -6]. Start with max = A[0] and i = 1, and record max and i before every condition check. What does the method return, and what would a starting value of 0 have returned instead?

Hints
  • Start max at -4, not at 0.
  • The comparison replaces max only when the current element is strictly larger.
  • Count how many passes actually run before the condition 3 < 3 is checked.
EASY

Take the sum loop and move the return statement inside the body. Trace it on [3, 7, 2] and identify the first check where the recorded state stops matching the loop's intended result.

Hints
  • Look at what happens on the first pass.
  • The state after the first pass tells you where the divergence begins.

Key Takeaways

  • A while loop traversal is controlled by a counter i compared against the array length, so each valid index is visited exactly once.
  • The sum loop starts total at 0 and adds A[i] before advancing i, and the maximum loop starts max at A[0] and begins its index at 1.
  • Hand tracing with a state record before each condition check is the most reliable way to find where a loop's result diverges from expectation.
  • The order of statements in a loop body and the placement of the return statement both change the result, so each should be checked on every trace.
  • Class invariants are named in this article's title, but the source material here develops only the loop-side reasoning.