java-foundations 23 min read

do-while, Fencepost, Debugging

The loop that always runs once, the separator pattern, and the five loop bugs you'll write this quarter

Four things today. First, do-while — the loop shape that runs its body before checking the condition, so the body always runs at least once. Second, the fencepost problem — why n items need n - 1 separators, and how to print a comma-separated list with no trailing comma. Third, break and continue — the two keywords that let you exit a loop early or skip the rest of one iteration. Fourth, the debugging playbook — every loop symptom mapped to its most likely cause and its first fix.

This is the synthesis lesson for Chapter 4. The new pieces are small — do-while, fencepost, and the break/continue escape hatches. The real work is having every loop tool from this chapter (definite, accumulator, sentinel, flag, nested, do-while) available when you need them.

In a nutshell

do-while is built for one specific problem: re-prompt the user until valid input arrives. It runs the body, then checks the condition — so the body always runs at least once. The loop repeats while the condition is true. Forget the trailing ; after the while(...) and the compiler will complain on the next line.

n items need n − 1 separators. Five fence posts need four rails. Printing a comma-separated list of 5 values needs 4 commas — and the easiest way is to print the first item outside the loop, then ", " + item on each subsequent pass.

Every loop bug has a symptom, a cause, and a first check. Memorize the five shapes: infinite loop (no update), infinite loop (wrong direction), off-by-one, zero iterations, one-too-many. Name the symptom first, then the cause — that order is the difference between guessing what broke and knowing what broke.

Quick reference

do-while syntax

do {
    // body — runs at least once
} while (condition);    // <-- semicolon is REQUIRED

Fencepost: comma-separated list, no trailing comma

System.out.print(items[0]);               // first item outside the loop
for (int i = 1; i < items.length; i++) {
    System.out.print(", " + items[i]);    // separator before each remaining item
}
System.out.println();

break and continue

for (int i = 0; i < n; i++) {
    if (/* found what we needed */) {
        break;      // exit the loop immediately
    }
    if (/* skip this one */) {
        continue;   // skip rest of body; go to next condition check
    }
    // normal work
}

Bug-signature table

Symptom Cause First check
Program hangs, same value repeats Body never changes the loop variable Is there an i++/i-- in the body?
Program hangs, variable is changing Condition can never become false Does the update drive the variable toward making the condition false?
Off-by-one (wrong count) < vs <=, or initial value off by one List the intended first and last values
Body never runs, no output Condition is false at entry Print the loop variable before the loop
One-too-many iterations <= where < is needed Trace the final iteration

Deep dive

1. The do-while shape and when to use it

A while loop checks its condition before running the body, which means the body can run zero times. That is the right default. For one specific problem — re-prompting the user until valid input arrives — you always want the body to run at least once. do-while is the Java construct for exactly that.

Plan. Read an integer repeatedly until the user types a positive number.

Scanner in = new Scanner(System.in);
int n;
do {
    System.out.print("Enter a positive integer: ");
    n = in.nextInt();
} while (n <= 0);
System.out.println("You entered: " + n);

Trace for a user who types -3, then 0, then 5:

Pass user types n after read Condition n <= 0
1 -3 -3 true — loop again
2 0 0 true — loop again
3 5 5 false — exit

After the loop: n = 5.

Sanity check. Trace for a user who types 7 on the very first prompt. The body runs once, n = 7, condition 7 <= 0 is false, loop exits. Body ran exactly once. That’s the “at least once” guarantee.

Pitfall — missing semicolon on the do-while closer.

do {
    // body
} while (count < 10)     // <-- missing ;
int x = 5;               // compile error on this line

The missing semicolon causes a syntax error. The fix is one character: add ; after the closing parenthesis.

Note on Python (CSCD 110). Python has no do-while. A common Python idiom is while True: ... if valid: break. In Java, the do-while shape makes the intent clearer: you loop because input was invalid, not until a break says so.

2. The fencepost problem

Picture a fence: five posts with rails between them. You need four rails — one between each adjacent pair. If you build five rails, one post has a rail on one side and a gap on the other. That’s the fencepost problem, and it appears every time you print a list of items with a separator between them.

n items need n − 1 separators. Printing n things with a separator between each pair means printing the separator n − 1 times, not n times.

Plan. Print the integers 1 through 5, comma-separated, no trailing comma.

System.out.print(1);                     // first item: no leading comma
for (int i = 2; i <= 5; i++) {
    System.out.print(", " + i);          // separator before each remaining item
}
System.out.println();

Output:

1, 2, 3, 4, 5

An alternative that also works — print the item and comma in the loop, but skip the comma on the last iteration:

for (int i = 1; i <= 5; i++) {
    System.out.print(i);
    if (i < 5) {
        System.out.print(", ");
    }
}
System.out.println();

Both are correct. The first (separator-before-each-remaining-item) is usually cleaner because it avoids the if inside the loop.

3. break and continue — the escape hatches

Most loops should run to their natural end — a counter reaches its bound, a sentinel arrives, a flag flips. For the cases where you need to bail out of the loop early, or skip the rest of one iteration without exiting, Java has two keywords: break and continue.

break exits the loop immediately. Execution jumps to the first line after the closing brace.

// Read n integers; report the first negative value (if any).
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int firstNegative = 0;
boolean found = false;
for (int i = 0; i < n; i++) {
    int x = in.nextInt();
    if (x < 0) {
        firstNegative = x;
        found = true;
        break;                 // exit the for loop right now
    }
}
if (found) {
    System.out.println("First negative: " + firstNegative);
} else {
    System.out.println("No negatives in the input.");
}

Without break, the same logic needs a flag-controlled while (lesson 4-c §6) plus a counter you maintain by hand. break lets you keep the natural for (int i = 0; i < n; i++) shape and bail out the moment the body finds what it was looking for.

continue skips the rest of the current iteration and goes straight to the next condition check. In a for loop, the update slot still fires; the rest of the body just doesn’t run for this pass.

// Sum only the even integers from 1 through n.
int sum = 0;
for (int i = 1; i <= n; i++) {
    if (i % 2 != 0) {
        continue;              // odd: skip the rest of this iteration
    }
    sum += i;                  // only even i reaches this line
}

In simple cases, an if filter (if (i % 2 == 0) sum += i;) reads more clearly than continue. Reach for continue when the skip condition is complex enough that nesting the real work inside an if would push everything too far to the right.

Pitfall — continue in a while skips the update. In a for loop, continue triggers the header’s update slot before the next condition check. In a while loop, the update lives in the body — so a continue placed before the update line creates an infinite loop, because the loop variable never changes. Either move the update above the continue, or rewrite the loop as a for.

Use sparingly. A loop with two breaks and a continue becomes hard to read and hard to trace. The natural shapes from lessons 4-a through 4-c handle most problems. Reach for break and continue when they make a complex loop simpler, not just shorter.

4. Three common off-by-one shapes

Broken code Symptom Fix
for (int i = 1; i < 5; i++) when you meant “1 through 5” Prints 4 items instead of 5 Change < to <=
for (int i = 0; i <= 5; i++) when you meant “0 through 4” Prints 6 items instead of 5 Change <= to <
System.out.print(i + ",") every iteration Trailing comma after last item Use the separator-before pattern or an if (i < n) guard

How to decide < vs <=. Ask two questions about the counter. (1) What is the first value the loop should process? That is the initial value. (2) What is the last value the loop should process? If the counter reaches that value, use <=. If the counter stops one step before, use <.

Example: “print 1 through 10” — first 1, last 10, so i <= 10. Example: “print 0 through 9 (ten items, zero-indexed)” — first 0, last 9, so i < 10 (or equivalently i <= 9).

5. The debugging playbook

When a loop misbehaves, name the symptom first. The symptom tells you which of five shapes to check. Confirm with a diagnostic println or a hand trace.

Infinite loop — counter not updated. Observation: program hangs; console floods with the same value. Cause: body never changes the loop variable. First check: is there an i++ or i-- inside the body?

Infinite loop — condition always true. Observation: program hangs; the variable is changing but in the wrong direction. Cause: the update cannot drive the condition toward false. First check: Does i-- combined with i < n where i starts at 5 ever terminate? (No — i heads away from the bound.)

Off-by-one — wrong count. Observation: output has one too many or one too few items; content is otherwise correct. Cause: < vs <=, or initial value off by one. First check: list the intended first and last values; verify the condition includes both.

Zero iterations — body never runs. Observation: no output from inside the loop; execution continues silently. Cause: condition is false at entry — initial value and condition are incompatible. First check: print the loop variable before the loop; verify the condition is true with that value.

One-too-many iterations. Observation: loop body runs one extra time; last output value is wrong. Cause: <= where < is needed, or counter starts one too low. First check: trace the final iteration on paper; ask whether the condition allows one extra pass.

6. The diagnostic println — where, what, when

Place one line at the top of the loop body; include the loop variable and any accumulator you are tracking.

// Add this as the first line of the body:
System.out.println("iter=" + i + " sum=" + sum);

What to do with the output:

  • No lines print. Body ran zero times. Condition was false at entry. Move the println to before the loop to see initial values.
  • Console floods. Infinite loop. The variable in the condition is not changing. Inspect the update.
  • Values look right but count is off by one. Off-by-one in the condition. Read the last printed value — that tells you where the loop stopped.

After fixing the bug, delete the diagnostic line. Do not submit code with debug prints in it. (Comment it out first if you are not sure you are done: // System.out.println(...);.)

7. Loop invariants — the sentence that proves the loop works

An invariant is a sentence that is true at the top of every iteration, before the body runs. Writing one forces you to say precisely what the loop is accomplishing, which makes it much easier to verify correctness.

int sum = 0;
// Invariant: at the top of this iteration, sum equals
//            the sum of the integers from 1 through (i - 1).
for (int i = 1; i <= n; i++) {
    sum += i;
}

Before iteration 1: sum = 0, which is the sum of the empty range — correct. Before iteration 5 (if n >= 5): sum = 1 + 2 + 3 + 4 = 10. The invariant holds. After the loop completes (i = n + 1), sum holds 1 + 2 + ... + n. Correctness follows directly from the invariant.

A while loop with a sentinel has an invariant of its own shape. It describes the state right before each condition check, including the priming-read variable.

int sum = 0;
int x = in.nextInt();                  // priming read
// Invariant: sum equals the total of all values read so far that
//            were not the sentinel; x holds the most recent input
//            and has not yet been processed.
while (x != -1) {
    sum += x;
    x = in.nextInt();
}

The invariant tells you exactly why the sentinel is never added to sum: when the loop exits, x = -1 is “the most recent input, not yet processed,” and the body that would have processed it never runs.

Invariants are not a grading formalism here — you will not be asked to prove them with symbols. You need to write one clear English sentence that a fellow programmer could check with a diagnostic println.

Practice: write the invariant for this loop in one sentence.
int count = 0;
for (int i = 1; i <= n; i++) {
    if (i % 2 == 0) {
        count++;
    }
}

Hint: what does count hold at the start of iteration i?

Invariant. At the top of iteration i, count equals the number of even integers in the range [1, i - 1].

Before iteration 1: count = 0, even-count of the empty range is 0 — correct. Before iteration 5 (if n >= 5): count = 2, which is the count of even values in [1, 4] (namely 2 and 4). The invariant holds.

8. Synthesis — English to Java, end to end

Problem. Read integers until the user types 0. Print the count of negative integers entered. (The 0 is not counted.)

Step 1. Variables: current (most recent read) and negatives (running count). Step 2. Start: priming read. Stop: current == 0. Step 3. Shape: while with a sentinel — count is unknown, body drives termination. Step 4. Pattern: sentinel + accumulator (we accumulate a count). Step 5. Write it.

Scanner in = new Scanner(System.in);
int negatives = 0;
System.out.print("Enter an integer (0 to stop): ");
int current = in.nextInt();
// Invariant: negatives equals the count of negative integers
//            read so far; current is not yet processed.
while (current != 0) {
    if (current < 0) {
        negatives++;
    }
    System.out.print("Enter an integer (0 to stop): ");
    current = in.nextInt();
}
System.out.println("Negative count: " + negatives);

Sample run (user types 3, -7, -2, 5, 0):

Enter an integer (0 to stop): 3
Enter an integer (0 to stop): -7
Enter an integer (0 to stop): -2
Enter an integer (0 to stop): 5
Enter an integer (0 to stop): 0
Negative count: 2

Notice the structure matches lesson 4-c’s sentinel pattern exactly: priming read, loop while not sentinel, process then re-read, never process the sentinel itself.


Before you leave

do-while guarantees the body runs at least once — use it for input-validation prompts. The fencepost rule says n items need n - 1 separators; print the first item outside the loop and separator-first on the rest. Every loop bug fits one of five shapes; name the symptom, find the cause, apply the first check.

That’s Chapter 4. Next week: methods — how to name a block of code and call it from anywhere. Once you have branching and loops, programs get long fast. Methods are how you break programs into named, reusable pieces.

Want to practice? The Week 4 quiz-prep set on the practice platform has do-while, fencepost, and mixed debugging problems mapped to this lesson.