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
breakto stop as soon as a search condition is satisfied — it avoids wasted iterations and often makes intent clearer than a boolean flag alone. - Use
continuefor early “skip this one” guard clauses at the top of a loop body instead of wrapping the rest of the body in a largeifblock; it keeps nesting shallow. - Reach for labeled
break/continueonly 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
returninstead; it’s often clearer than any label. - In hand-written
whileordo-whileloops, double-check that any counter or state update the loop depends on happens before acontinuecan skip it, or restructure as aforloop where the update is guaranteed to run. - Remember a
breakinside aswitchnested in a loop only exits theswitch— use a label if you actually need to stop the loop from there. - Avoid stacking multiple unlabeled
break/continuestatements deep inside nested loops purely to avoid restructuring the logic; too many can make control flow as hard to follow as an old-fashionedgoto.
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
continueto skip the ones that are. - Exercise 2: Given a 3×3
int[][]array, write a program that uses a labeledbreakto 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 usesbreakto stop as soon as it encounters a 6, then prints how many rolls it took (the position of the 6, counting from 1).
Summary
breakimmediately exits the nearest enclosing loop (orswitch); the loop’s condition is never checked again.continueskips the rest of the current iteration and moves on to the next one, without leaving the loop.- In a
forloop,continuestill runs the update expression before re-checking the condition; in awhile/do-whileloop it jumps straight to the condition, skipping any code below it. - Labels (
name:before a loop) letbreak label;andcontinue label;target an outer loop from inside a nested one. - A plain
breakinside nested loops only exits the innermost loop — use a label if you need to exit an outer loop too. - A
breakinside aswitchnested in a loop only exits theswitch, 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/continueto keep loops readable, but don’t let deeply nested labeled jumps replace code that would be clearer as a separate method withreturn.
