break, continue, and goto
Most of the time, loops run from start to finish under the control of their condition expression. But situations arise where you need to exit early, skip an iteration, or jump over code entirely. C++ provides three statements for this: break, continue, and the legacy goto. Understanding all three — including when not to use them — makes your loop logic cleaner and harder to misread.
continue — skip this iteration
The continuestatement causes the current loop iteration to end immediately. Execution transfers to the end of the loop body, the loop's increment expression runs (for a for loop), and the condition is tested for the next iteration. The loop itself does not end — only the current pass is cut short.
continue; // jump to the end of the current loop iteration
A classic use case: you are iterating over a range but want to process only the elements that meet some condition, while silently ignoring the rest. Here, a loop prints every printable ASCII character with its hex and decimal codes, skipping non-printable characters using continue:
#include <cctype> // std::isprint
#include <print>
int main() {
// Header
std::println("{:^11}{:^11}{:^11}", "Character", "Hexadecimal", "Decimal");
for (int ch {}; ch <= 127; ++ch) {
if (!std::isprint(ch))
continue; // not printable — skip to the next iteration
std::println("{0:^11}{0:^11X}{0:^11d}", static_cast<char>(ch));
}
}
// Prints a table of all 95 printable ASCII characters with their codesWhen the character is not printable, continue fires and the println line is skipped. The loop increments ch and checks the condition again for the next character.
Consider the if-rewrite first
A continue can almost always be replaced by an if that guards the body. The if version often reads more clearly because the logic is positive rather than double-negated:
// With continue — logic is "if NOT printable, skip":
for (int ch {}; ch <= 127; ++ch) {
if (!std::isprint(ch)) continue;
std::println("{}", static_cast<char>(ch));
}
// With if-guard — logic is "if printable, print":
for (int ch {}; ch <= 127; ++ch) {
if (std::isprint(ch))
std::println("{}", static_cast<char>(ch));
}
// Both produce identical output; the if-guard is easier to read at a glanceThe main advantage continue has over the if-rewrite is that it can simplify deep nesting: when a long body would otherwise need to be indented three or four levels, an early continueat the top keeps the main logic flat. Use it when it genuinely reduces nesting; avoid it when it just obscures which iterations are the “normal” ones.
break — exit the loop entirely
While continue skips one iteration, break ends the entire loop. When the compiler executes break inside a loop, execution jumps immediately to the first statement afterthe loop's closing brace. (You have already met break inside switch statements, where it plays the same role.)
for (int i {0}; i < 10; ++i) {
if (i == 5)
break; // exit the loop when i reaches 5
std::println("{}", i);
}
// Output: 0 1 2 3 4
// The loop body never executes for i = 5, 6, 7, 8, 9break only exits the innermost loop or switch. If you have nested loops and need to exit both at once, you need either a flag variable or a restructuring with a function return.
// break only exits the inner loop:
for (int i {0}; i < 3; ++i) {
for (int j {0}; j < 3; ++j) {
if (j == 1)
break; // exits the j-loop; the i-loop continues
std::print("{},{} ", i, j);
}
}
// Output: 0,0 1,0 2,0
// Exiting both loops with a flag:
bool done {false};
for (int i {0}; i < 3 && !done; ++i) {
for (int j {0}; j < 3; ++j) {
if (i == 1 && j == 1) { done = true; break; }
std::print("{},{} ", i, j);
}
}
// Output: 0,0 0,1 0,2 1,0Indefinite loops — for(;;) and while(true)
break is most commonly paired with an indefinite loop — a loop that has no termination condition in its header, so it would run forever unless something inside it calls break or return. The two most common forms are:
// All three expressions of the for loop are omitted; semicolons stay:
for (;;) {
// runs forever unless break or return is hit
}
// while with a literal true:
while (true) {
// same effect
}Indefinite loops are practical when you do not know in advance how many iterations you will need — for example, prompting a user until they enter a valid value, or reading data until a sentinel value signals the end. Here is input validation with an indefinite loop capped at a maximum number of attempts:
const int min_size {2};
const int max_size {20};
const int max_tries {3};
int table {};
for (int count {1}; ; ++count) { // indefinite for loop
std::print("Table size ({} to {})? ", min_size, max_size);
std::cin >> table;
if (table >= min_size && table <= max_size)
break; // valid input — exit loop
if (count < max_tries) {
std::println("Invalid — try again.");
} else {
std::println("Too many invalid attempts. Exiting.");
return 1;
}
}
// Execution continues here only if the user entered a valid table sizeHere is another classic use: reading values until the user types 0, then processing the collected data. The break serves as the only exit path:
std::vector<double> values;
while (true) {
double input {};
std::print("Enter a value (0 to stop): ");
std::cin >> input;
if (input == 0.0)
break; // sentinel value — done collecting
values.push_back(input);
}
std::println("You entered {} values.", values.size());Tip: prefer conditions over break when possible
Most C++ programmers advise putting termination conditions directly in the for or while header rather than burying a break inside the body. A reader scanning the loop header immediately sees when the loop ends; a hidden break deep inside the body is easy to miss. Use indefinite loops when the exit condition genuinely cannot be expressed cleanly as a header condition — not as a shortcut for writing the condition properly.
goto — and why you should not use it
goto is the oldest flow-control statement inherited from C. It jumps unconditionally to any labeled statement within the same function. A label is an identifier followed by a colon:
// Syntactically valid — but almost never written in modern C++:
int x {0};
start:
std::println("x = {}", x);
++x;
if (x < 5)
goto start; // jump back to the label
std::println("Done");The above is just a loop written with goto. C++ gives you far better tools: for, while, do-while, break, and continue. These structured alternatives are readable in isolation — you understand the control flow just by reading the loop header. With goto, you need to mentally search the entire function for where the label is and what state the program is in when it arrives there.
There are further, deeper problems with gotoin C++ specifically: it can jump over variable declarations, which the compiler may or may not allow depending on whether those variables have non-trivial constructors. The rules are complex and error-prone. The C++ Core Guidelines (ES.76) say simply: “avoid goto.”
The only defensible use of goto in C++ is escaping from multiple levels of nested loops in one shot — but even that is cleaner as a function return, a flag variable, or a lambda. In practice, you will encounter goto in old C code and legacy codebases. For any new code you write, treat it as off-limits.
// The "break from nested loops" pattern — compare three approaches:
// 1. goto — technically works, widely considered bad style:
for (int i = 0; i < rows; ++i)
for (int j = 0; j < cols; ++j)
if (grid[i][j] == target)
goto found;
found:
// ... handle found case
// 2. flag variable — clear, no goto:
bool found {false};
for (int i = 0; i < rows && !found; ++i)
for (int j = 0; j < cols; ++j)
if (grid[i][j] == target) { found = true; break; }
// 3. separate function with return — cleanest:
auto find_in_grid = [&]() -> std::optional<std::pair<int,int>> {
for (int i = 0; i < rows; ++i)
for (int j = 0; j < cols; ++j)
if (grid[i][j] == target)
return {{i, j}};
return std::nullopt;
};
auto pos = find_in_grid();Key rules to remember
continue skips the current iteration; break ends the loop
continue moves to the next iteration (running the for-loop increment first). break jumps past the closing brace of the loop. Neither affects outer loops.
break only exits one level
In nested loops, break exits the innermost loop only. Use a flag variable or extract the loops into a function to exit multiple levels cleanly.
Prefer an if-guard over continue when it reads more clearly
if (condition) { body; } is often clearer than if (!condition) continue; body; because the logic is positive. Use continue when it removes deep nesting.
Indefinite loops are fine — but put the exit condition somewhere visible
for(;;) and while(true) are idiomatic. The risk is that break statements buried inside the body are easy to overlook. Name variables clearly and keep the exit logic close to the top of the loop body.
Never write goto in new C++ code
Every use of goto has a structured equivalent that is easier to read and reason about. In C++ specifically, goto can interact badly with variable initialization rules. The C++ Core Guidelines ban it.