Java Break and Continue

Loops don’t always need to run to completion. Sometimes you want to stop searching the moment you find what you’re after, or skip an item that doesn’t matter without processing the rest of your logic for it. Java gives you two keywords for exactly this: break, which exits a loop immediately, and continue, which skips straight to the next iteration. They look simple, but combined with nested loops and labels they’re a frequent source of subtle bugs — this lesson covers how they actually work, not just what they do.

Overview: How break and continue Work

Loops in Java — for, while, do-while, and the enhanced for-each loop — normally run top to bottom: check a condition, run the body, update state, repeat until the condition is false. break and continue are jump statements you place inside a loop body to override that normal flow.

break immediately terminates the nearest enclosing loop. The instant the JVM executes a break, it abandons whatever is left of the current iteration, skips every remaining iteration, and jumps to the first statement after the loop — the loop’s condition is never re-checked again. break is also used inside a switch statement’s case blocks to stop execution from falling through into the next case. It’s the same keyword and the same underlying jump, just applied to a different kind of block, which is exactly the source of one of the classic mistakes covered later.

continue is less drastic: it skips only the rest of the current iteration’s body and moves on to the next one, without leaving the loop. What “moving on” means depends on the loop type. In a for loop, continue jumps to the update expression (the i++ part) and then re-checks the condition, so the loop still advances normally. In a while or do-while loop there is no separate update expression, so continue jumps straight to the condition check — any code physically written after the continue, including a counter increment you wrote by hand, is skipped entirely on that pass.

By default, both statements act on the nearest enclosing loop — the innermost one containing them. When loops are nested and you need to control an outer loop from inside an inner one, Java lets you attach a label to the outer loop (an identifier followed by a colon, like outer:) and then write break outer; or continue outer; from anywhere inside it. Labels are the only form of controlled jump Java exposes to programmers — the language reserves the word goto but never implements it, precisely because unrestricted jumps make code hard to follow. A labeled break or continue can only target a loop that lexically encloses it; you can’t jump into a loop, or to an arbitrary line elsewhere in the program.

Syntax

Combined with labels, there are effectively four forms you’ll use:

break;
break label;

continue;
continue label;

label:
for (...) {
    // loop body
}
Form What it does
break; Immediately exits the nearest enclosing loop or switch statement.
continue; Skips the rest of the current loop iteration and proceeds to the next one.
label: Placed directly before a loop, gives it a name so it can be targeted from a nested loop.
break label; Exits the loop marked by label, even if it isn’t the innermost loop.
continue label; Skips to the next iteration of the loop marked by label, even from inside a nested loop.

A label must sit directly above the statement it names, with nothing else in between, and it must be a loop the break or continue statement is textually nested inside. Label names follow the same rules as variable names; by convention they’re written in lowerCamelCase, such as outer or searchLoop, and don’t collide with variable or method names elsewhere in the class.

Examples

Example 1: break — Stopping a Search Early

public class Main {
    public static void main(String[] args) {
        int target = -1;
        for (int i = 1; i <= 100; i++) {
            if (i % 7 == 0 && i % 5 == 0) {
                target = i;
                break;
            }
        }
        System.out.println("First number divisible by both 7 and 5: " + target);
    }
}

Output:

First number divisible by both 7 and 5: 35

The loop checks every number from 1 to 100. As soon as it finds one divisible by both 7 and 5 (that is, by 35), it stores it in target and calls break. The remaining 65 iterations never run. Without the break, the loop would keep going all the way to 100, overwriting target with the next multiple of 35 it found (70), leaving you with the wrong match instead of the first one.

Example 2: continue — Skipping Unwanted Iterations

public class Main {
    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
            if (i % 2 == 0) {
                continue;
            }
            System.out.print(i + " ");
        }
        System.out.println();
    }
}

Output:

1 3 5 7 9 

continue skips the print statement whenever i is even, so only odd numbers reach System.out.print. The loop’s update expression, i++, still runs every single time — continue in a for loop jumps to the update expression, not past it, so the loop keeps counting normally from 1 to 10.

Example 3: Labeled break — Escaping Nested Loops

public class Main {
    public static void main(String[] args) {
        int[] arr = {2, 7, 11, 15};
        int targetSum = 9;
        boolean found = false;

        outer:
        for (int i = 0; i < arr.length; i++) {
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[i] + arr[j] == targetSum) {
                    System.out.println("Found pair: " + arr[i] + " + " + arr[j] + " = " + targetSum);
                    found = true;
                    break outer;
                }
            }
        }

        if (!found) {
            System.out.println("No pair found.");
        }
    }
}

Output:

Found pair: 2 + 7 = 9

This program looks for two numbers in arr that add up to targetSum. The outer loop carries the label outer:. Once a matching pair is found, break outer; exits both loops in a single step. Without the label, a plain break would only stop the inner j loop, and the outer i loop would keep running pointlessly — wasting work and, in code that updates a result variable on every match, potentially overwriting the correct answer with a later, unwanted one.

Example 4: Labeled continue — Skipping an Entire Outer Iteration

public class Main {
    public static void main(String[] args) {
        outerLoop:
        for (int i = 1; i <= 3; i++) {
            for (int j = 1; j <= 3; j++) {
                if (i == j) {
                    continue outerLoop;
                }
                System.out.println(i + ", " + j);
            }
        }
    }
}

Output:

2, 1
3, 1
3, 2

continue outerLoop; behaves differently from break outerLoop; — it doesn’t leave the outer loop, it just abandons the rest of the current outer iteration and jumps to the outer loop’s next value of i. Trace it by hand: when i equals 1 and j equals 1, the label sends control straight to i = 2, so the pair (1, 1) never prints and the rest of that inner loop (j = 2, j = 3) never even runs. The same thing happens again when i equals j at (2, 2), which is why the output jumps from 2, 1 straight to 3, 1.

Under the Hood: How break and continue Work

Java source-level loops don’t exist as such once your code is compiled — javac lowers for, while, and do-while into a sequence of comparison opcodes and unconditional jump instructions called goto in the class file’s bytecode. There is no “loop” concept at the bytecode level, only conditional branches and jumps.

break compiles to a goto whose target is the first instruction after the loop. continue compiles to a goto whose target depends on the loop type: in a for loop, the target is the update expression, so it still executes before the condition is re-tested; in a while or do-while loop, the target is the condition check itself, so any statement physically below the continue — including a hand-written counter increment — is skipped on that pass. This single difference explains a lot of real-world bugs in hand-rolled while loops.

Labeled break and continue work the same way, except the jump target is fixed to the boundary of the labeled loop rather than the innermost one. This is resolved entirely at compile time by matching the label text to an enclosing labeled statement — there’s no runtime lookup, and the compiler rejects the code if the label doesn’t exist or the statement isn’t lexically nested inside it.

Common Mistakes

Mistake 1: Forgetting break Only Exits the Innermost Loop

It’s easy to assume break stops “the search” entirely, when it only stops the loop it’s physically written inside.

Wrong:

public class Main {
    public static void main(String[] args) {
        int[][] grid = {
            {1, 2, 3},
            {4, 5, 2},
            {2, 8, 9}
        };
        int target = 2;
        int foundRow = -1;
        int foundCol = -1;

        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[i].length; j++) {
                if (grid[i][j] == target) {
                    foundRow = i;
                    foundCol = j;
                    break;
                }
            }
        }
        System.out.println("First match at row " + foundRow + ", col " + foundCol);
    }
}

Output:

First match at row 2, col 0

The break only exits the inner j loop when a match is found; the outer i loop keeps running and finds the other two matches, overwriting foundRow/foundCol each time. The println label says “First match” but the value is actually the last match in the grid — a wrong answer that still compiles and runs without error, which makes it dangerous.

Corrected (using a labeled break):

public class Main {
    public static void main(String[] args) {
        int[][] grid = {
            {1, 2, 3},
            {4, 5, 2},
            {2, 8, 9}
        };
        int target = 2;
        int foundRow = -1;
        int foundCol = -1;

        searchLoop:
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[i].length; j++) {
                if (grid[i][j] == target) {
                    foundRow = i;
                    foundCol = j;
                    break searchLoop;
                }
            }
        }
        System.out.println("First match at row " + foundRow + ", col " + foundCol);
    }
}

Output:

First match at row 0, col 1

break searchLoop; exits both loops the moment the first match is found, so the result is correct and no wasted iterations occur.

Mistake 2: Expecting break in a switch to Exit the Enclosing Loop

Because break is used inside both loops and switch statements, it’s easy to assume a break inside a switch that’s nested in a loop will stop the loop. It won’t — it only exits the switch.

Wrong:

public class Main {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            switch (i) {
                case 3:
                    break;
                default:
                    System.out.println("i = " + i);
            }
        }
        System.out.println("Loop finished");
    }
}

Output:

i = 0
i = 1
i = 2
i = 4
Loop finished

The intent looks like “stop everything once i reaches 3″, but the break in case 3 only exits the switch block for that iteration; the for loop doesn’t notice and keeps going, printing i = 4 right after.

Corrected (using a labeled break to target the loop, not the switch):

public class Main {
    public static void main(String[] args) {
        loop:
        for (int i = 0; i < 5; i++) {
            switch (i) {
                case 3:
                    break loop;
                default:
                    System.out.println("i = " + i);
            }
        }
        System.out.println("Loop finished");
    }
}

Output:

i = 0
i = 1
i = 2
Loop finished

break loop; explicitly names the for loop as its target, so it exits the loop itself, not just the surrounding switch.

Best Practices

  • Use break to stop as soon as a search condition is satisfied — it avoids wasted iterations and often makes intent clearer than a boolean flag alone.
  • Use continue for early “skip this one” guard clauses at the top of a loop body instead of wrapping the rest of the body in a large if block; it keeps nesting shallow.
  • Reach for labeled break/continue only when you genuinely need to control an outer loop from inside a nested one — don’t label loops that don’t need it.
  • When exiting deeply nested loops becomes hard to read even with labels, consider extracting the search into its own method and using return instead; it’s often clearer than any label.
  • In hand-written while or do-while loops, double-check that any counter or state update the loop depends on happens before a continue can skip it, or restructure as a for loop where the update is guaranteed to run.
  • Remember a break inside a switch nested in a loop only exits the switch — use a label if you actually need to stop the loop from there.
  • Avoid stacking multiple unlabeled break/continue statements deep inside nested loops purely to avoid restructuring the logic; too many can make control flow as hard to follow as an old-fashioned goto.

Practice Exercises

  • Exercise 1: Write a program that loops from 1 to 50 and prints every number that is not divisible by 3 or 5, using continue to skip the ones that are.
  • Exercise 2: Given a 3×3 int[][] array, write a program that uses a labeled break to find and print the row and column of the first negative number, or prints “No negative numbers found” if there isn’t one.
  • Exercise 3: Given a fixed array of simulated die rolls, for example {3, 5, 2, 6, 1, 4}, write a program that iterates through it and uses break to stop as soon as it encounters a 6, then prints how many rolls it took (the position of the 6, counting from 1).

Summary

  • break immediately exits the nearest enclosing loop (or switch); the loop’s condition is never checked again.
  • continue skips the rest of the current iteration and moves on to the next one, without leaving the loop.
  • In a for loop, continue still runs the update expression before re-checking the condition; in a while/do-while loop it jumps straight to the condition, skipping any code below it.
  • Labels (name: before a loop) let break label; and continue label; target an outer loop from inside a nested one.
  • A plain break inside nested loops only exits the innermost loop — use a label if you need to exit an outer loop too.
  • A break inside a switch nested in a loop only exits the switch, not the loop — a common source of confusion.
  • Under the hood, both statements compile to goto-style jumps in the bytecode, resolved entirely at compile time.
  • Use break/continue to keep loops readable, but don’t let deeply nested labeled jumps replace code that would be clearer as a separate method with return.