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.
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
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.
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.
Tracing arraySum on [3, 7, 2]
Trace the sum loop on the array [3, 7, 2], recording total and i before every condition check.
- 1Check 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.
- 2Check 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.
- 3Check 2: i = 2
The condition 2 < 3 is true. The addition uses A[2] = 2, giving total = 12. Then i becomes 3.
- 4Check 3: i = 3
The condition 3 < 3 is false, so no pass runs. The loop ends, and the return statement gives total = 12.
The sum loop returns 12, which equals 3 + 7 + 2.
| Check | i before check | Condition i < 3 | total after pass | i after pass |
|---|---|---|---|---|
| 1 | 0 | true | 3 | 1 |
| 2 | 1 | true | 10 | 2 |
| 3 | 2 | true | 12 | 3 |
| 4 | 3 | false | 12 (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.
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.
total = total + A[i]
i = i + 1The addition reads the current element, and only then does the index advance. Each element is added once.
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.
- 1Check 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.
- 2Check 1: i = 1
The index moves to 2 first. The addition reads A[2] = 2, giving total = 9.
- 3Check 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.
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.
- 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.
Tracing the maximum loop on [3, 7, 2]
Trace the maximum loop on [3, 7, 2], starting with max = A[0] and i = 1.
- 1Start: 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.
- 2Check: 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.
- 3Check: i = 3
The condition 3 < 3 is false, so the loop ends and returns max = 7.
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 likeFor the array [-5, -2, -9], a max loop that starts at 0 never changes max and returns 0.
Why it failsThe loop can only ever select an element it compares, and 0 is not in the array.
Do this insteadStart 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 likeSwapping i = i + 1 and total = total + A[i] makes [3, 7, 2] return 9 instead of 12.
Why it failsThe addition reads the element after the intended one, so every later step is shifted.
Do this insteadUse A[i] first, then advance i at the end of the body.
Common mistake Returning from inside the loop
What it looks likePlacing return total inside the body of the sum loop on [3, 7, 2] returns 3.
Why it failsThe method stops after the first pass, so the other elements are never processed.
Do this insteadPlace 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.
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.
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.