Java / Basic / Lesson 3
Conditions, loops, and invariants
Translate decisions and repeated work into control flow whose boundaries and termination can be explained before execution.
On this page
Lesson goals
- Choose if, switch, for, and while from the problem.
- Trace a loop invariant and termination measure.
- Test empty and boundary cases.
State the decision in domain terms
Start with mutually understood rules: a nonnegative score below 50 is a retry, 50 through 100 passes, and all other values are invalid. Validate the domain before assigning a category. An if/else chain should make each accepted case and its boundary visible. A final else is not automatically an error case; it means everything not matched earlier.
Use braces even for short branches so later edits do not change scope accidentally. Name boolean conditions when they communicate intent better than a dense expression. && and
short-circuit from left to right, which can protect a null-sensitive check or avoid expensive work. Parentheses should express grouping clearly rather than relying on a reader to memorize every precedence rule.
Give a counted loop a precise range
A for loop makes initialization, continuation, and progress visible together. For the inclusive range 1 through 4, begin at 1, continue while i <= 4, and increment after the body. The accumulator starts at zero because zero is the additive identity. After each completed iteration it equals the sum of values already processed. That statement is the loop invariant.
The example prints 10 because it adds 1, 2, 3, and 4 exactly once. Trace the counter, accumulator before the body, and accumulator after it in a small table on paper. An empty range should produce the identity result when that is the intended contract. Do not initialize an accumulator to the first element unless empty input has been handled separately.
public class Main {
public static void main(String[] args) throws Exception {
int total = 0;
for (int i = 1; i <= 4; i++) { total += i; }
System.out.println(total);
}
}
10
The four additions preserve the invariant that total contains exactly the sum of processed values.
Prove progress in a while loop
Use while when repetition depends on state rather than an obvious fixed count. State what becomes closer to termination on every iteration. In a bounded retry loop that measure might be remaining attempts; in a search it may be the unexamined suffix. A condition that never changes creates a logical infinite loop even when the syntax compiles cleanly.
A do/while executes its body at least once, so it is inappropriate when zero iterations are a valid requirement unless the contract accounts for that. break exits the nearest loop or switch; continue advances to the next iteration and can accidentally skip a necessary progress update in a while loop. Prefer a simple visible progression before adding control-flow shortcuts.
Use a switch expression without fall-through
Java 17 supports standard switch expressions with arrow labels. An expression must produce a value on every accepted path; a default handles other input unless exhaustive language rules already cover it. The example maps a known status to a delivery label. Arrow labels do not fall through into the next case, unlike traditional colon-style statement groups unless deliberately controlled.
A switch expression is useful for a finite mapping, while if handles general inequalities and compound conditions naturally. Do not encode an open-ended range as hundreds of case labels. For business state, an enum can communicate the finite domain more clearly than arbitrary integers, although unknown external input still needs validation before conversion to that enum.
public class Main {
public static void main(String[] args) throws Exception {
String state = "READY";
String label = switch (state) {
case "READY" -> "dispatch";
case "WAITING" -> "hold";
default -> "review";
};
System.out.println(label);
}
}
dispatch
READY selects one arrow branch and produces dispatch; no next branch runs afterward.
Review control flow with counterexamples
For each branch, choose values immediately below, at, and above its boundary. For loops, check zero, one, and several iterations and verify the final counter state. An off-by-one error is often a disagreement between inclusive and exclusive endpoints, not an arithmetic mystery. Use half-open ranges for array indices because legal positions run from zero up to but excluding length.
A correct trace explains both the answer and why no extra work occurs. Separate a loop's mechanics from the operation performed in its body, and keep side effects small. When a program reads input repeatedly, treat end-of-input, invalid input, and a legitimate sentinel as distinct states. Otherwise a rejected value can accidentally become either an endless retry or a valid calculation.
Common errors and fixes
ArrayIndexOutOfBoundsException
- Why it happens
- A loop used <= length for zero-based indices.
- How to fix it
- Use i < length and validate any externally supplied index.
- Prevent it next time
- Test empty and single-element arrays.
Loop never terminates
- Why it happens
- The body does not make progress toward a false condition.
- How to fix it
- Identify and update a termination measure on every path.
- Prevent it next time
- Trace continue and error branches.
A boundary score is misclassified
- Why it happens
- Branches disagree about inclusive endpoints or skip input validation.
- How to fix it
- State accepted ranges and validate first.
- Prevent it next time
- Test values directly around every boundary.
Check your understanding
1. How many iterations does for (int i=0; i<3; i++) perform?
Show worked solution
Three, with i equal to 0, 1, and 2 in the body. After the final increment i is 3 and the condition fails. Changing < to <= adds an iteration at 3, which is invalid for a length-three array.
2. Design a retry counter allowing at most three attempts, including the first.
Show worked solution
Start attempts at zero, require attempts < 3, and increment exactly once per attempted operation, including rejected attempts. Stop immediately on success. Test immediate success, third-attempt success, and three failures; do not interpret three retries as three total attempts accidentally.
Key takeaways
- Write accepted ranges before branches.
- A loop needs an invariant and progress toward termination.
- Test endpoints and zero work explicitly.