Loops are where programs do their real work: adding up scores, searching for a name, counting occurrences, finding the largest value. After you have written a few dozen loops, you notice they are not random — they fall into a small set of patterns. Recognising the pattern lets you predict exactly what the loop will produce without mentally stepping through every single iteration.
This lesson covers the three patterns you will use most often: the accumulator (build a result piece by piece), the search (stop when you find what you need), and the min / max (keep the best value seen so far). We will also meet the invariant: a simple statement that stays true every time the loop body runs, which is the secret to knowing a loop is correct before you run it.
The accumulator pattern
The accumulator pattern builds a result one element at a time. Start with an initial value before the loop — 0 for a sum, 1 for a product, an empty string for concatenation. Then, for every element, update the accumulator with that element. When the loop ends, the accumulator holds the final answer.
The pattern looks like this:
int total = 0;
for (int value : values) {
total = total + value;
}
The key to reading an accumulator loop is to ask: what does the variable hold after processing the first N items? If the answer is obvious — total holds the sum of the first N items — the loop is correct. That simple question is the germ of a loop invariant.
flowchart LR INIT["total = 0"] --> L["for each value"] L --> U["total = total + value"] U --> L L -->|"done"| R["total = sum of all values"] style INIT fill:#c2410c,color:#fff style R fill:#166534,color:#fff
public class Main {
public static void main(String[] args) {
int[] scores = {4, 7, 2, 9};
int sum = 0;
int product = 1;
for (int s : scores) {
sum = sum + s;
product = product * s;
}
System.out.println("Sum: " + sum);
System.out.println("Product: " + product);
}
}
Search patterns: find-first and find-all
A search loop looks for something that satisfies a condition. The simplest version checks every element and keeps going — a find-all that might count matches or collect them. The more common version stops the instant it finds a match, using break. That is a find-first search, and it saves work: if the answer is in the first ten elements of a million-element list, you do not process the other 999,990.
A search loop needs a way to report not found. The usual idiom is to initialise a result variable to a sentinel value — -1 for an index, false for a boolean flag — and overwrite it only when a match is found. After the loop, check whether the sentinel is still there.
flowchart TD
subgraph FF["Find-first"]
F1["start loop"] --> F2{"match?"}
F2 -->|"yes"| F3["record result"]
F3 --> F4["break"]
F2 -->|"no"| F5["continue"]
F5 --> F2
end
subgraph FA["Find-all"]
A1["start loop"] --> A2{"match?"}
A2 -->|"yes"| A3["record result"]
A3 --> A2
A2 -->|"no"| A4["continue"]
A4 --> A2
end
style FF fill:#3776ab,color:#fff
style FA fill:#166534,color:#fff
public class Main {
public static void main(String[] args) {
int[] temps = {18, 22, 15, 29, 12};
int firstHot = -1;
for (int i = 0; i < temps.length; i++) {
if (temps[i] > 20) {
firstHot = i;
break;
}
}
System.out.println("First hot index: " + firstHot);
int max = temps[0];
for (int t : temps) {
if (t > max) {
max = t;
}
}
System.out.println("Max temp: " + max);
}
}
The min / max pattern
Finding the smallest or largest value is a variation of the accumulator pattern. Instead of adding each element to a total, you compare each element to the best value seen so far and keep the winner. Initialise max to the first element (or to Integer.MIN_VALUE if the array might be empty), then walk through every element. If the current element is larger, it becomes the new max.
This pattern works for any type that can be compared: integers, doubles, even strings (using .compareTo). The same structure gives you min if you flip the comparison.
Loop invariants: the sanity check
A loop invariant is a statement that is true before the loop starts and remains true after every iteration. It is not magic — it is just a way to describe what the loop has accomplished so far.
For the sum accumulator, the invariant is: sum equals the total of all elements processed so far. Before the loop, zero elements have been processed and sum is 0, so the invariant holds. After processing the third element, sum holds the total of the first three, so it still holds. When the loop ends, every element has been processed, so sum is the total of all of them.
For the max search, the invariant is: max is the largest value seen so far. Before the loop, max is the first element, so it is trivially the largest of the one-element prefix. After each iteration, either max keeps its title or the challenger takes it — either way, the invariant holds.
Invariants do not change how the code runs; they change how confidently you can read it. If you can state a clear invariant, the loop is probably correct. If you cannot, it probably has a bug.
flowchart TD
subgraph LT["i < 5"]
L1["i = 0"] --> L2["i = 1"]
L2 --> L3["i = 2"]
L3 --> L4["i = 3"]
L4 --> L5["i = 4"]
L5 --> L6["stop: 5 passes"]
end
subgraph LE["i <= 5"]
E1["i = 0"] --> E2["i = 1"]
E2 --> E3["i = 2"]
E3 --> E4["i = 3"]
E4 --> E5["i = 4"]
E5 --> E6["i = 5"]
E6 --> E7["stop: 6 passes"]
end
style LT fill:#3776ab,color:#fff
style LE fill:#b45309,color:#fff
Accumulator check. Predict the exact output (two lines) — track the sum and the product through every iteration.
public class Main {
public static void main(String[] args) {
int[] values = {2, 3, 4};
int sum = 0;
int product = 1;
for (int v : values) {
sum = sum + v;
product = product * v;
}
System.out.println(sum);
System.out.println(product);
}
}
sum: 0 + 2 = 2, then 2 + 3 = 5, then 5 + 4 = 9.
product: 1 * 2 = 2, then 2 * 3 = 6, then 6 * 4 = 24.
The product starts at 1, not 0, because multiplying by 0 would destroy the result.
Search with break. Predict the exact output (two lines) — the loop stops early when it finds a match.
public class Main {
public static void main(String[] args) {
int[] nums = {5, 8, 3, 12, 4};
int target = 12;
int index = -1;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == target) {
index = i;
break;
}
}
System.out.println(index);
System.out.println(nums[0]);
}
}
12 is at index 3 (0-based), so the loop stops there and index becomes 3.
break exits the loop immediately; the remaining elements are never checked.
nums[0] is 5, and it is printed regardless of the search result.
For a sum accumulator int total = 0; followed by a loop that adds each array element to total, what is the loop invariant?
A loop invariant describes what has been accomplished so far. After processing the first N elements, total holds their sum. That is true before the loop (N=0, total=0) and after every iteration.
Off-by-one trap. This loop has the classic bug, but a catch reports it instead of crashing. Predict the exact output (two lines) — how many elements were added before it went wrong?
public class Main {
public static void main(String[] args) {
int[] arr = {10, 20, 30};
int sum = 0;
try {
for (int i = 0; i <= arr.length; i++) {
sum = sum + arr[i];
}
System.out.println("finished: " + sum);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("crashed reading index " + arr.length);
System.out.println("sum before the crash: " + sum);
}
}
}
arr has 3 elements, so valid indices are 0, 1, 2. arr.length is 3.
The condition i <= arr.length lets i reach 3. Passes 0, 1 and 2 all succeed first — so the sum is complete and correct before anything goes wrong.
That is what makes off-by-one dangerous: the loop does the right work, then takes exactly one step too many. 'finished:' never prints, because the crash happens inside the loop.
Min/max transfer. Predict the exact output (two lines) — track the largest and smallest values seen.
public class Main {
public static void main(String[] args) {
int[] nums = {7, 2, 9, 3, 9};
int max = nums[0];
int min = nums[0];
for (int n : nums) {
if (n > max) {
max = n;
}
if (n < min) {
min = n;
}
}
System.out.println(max);
System.out.println(min);
}
}
max starts at 7. It updates to 9 when n is 9, and stays 9 afterwards.
min starts at 7. It updates to 2 when n is 2, and never goes lower.
The second 9 does not change max because 9 > 9 is false.
Recap
- The accumulator pattern builds a result one element at a time: initialise, loop, update.
- A search loop stops early with
breakwhen it finds a match; otherwise it processes every element. - The min / max pattern keeps the best value seen so far, updating whenever a better candidate appears.
- A loop invariant is a statement true before and after every iteration. It is the best tool for spotting off-by-one errors before you run the code.
- The off-by-one trap is the most common loop bug: use
<for zero-based indices, and double-check that the condition stops exactly where it should.
Next you will learn how break and continue give you finer control over loop flow, and how labelled loops let you escape nested structures cleanly.