Course outline

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.

About 5 min readContent reviewed
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.

java A loop with a stated invariant
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);
 }
}
Expected output
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.

java A standard Java 17 switch expression
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);
 }
}
Expected output
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.

Official references