Exiting loops and iterations early

Loops normally run until their condition becomes false. But sometimes you need more control - exiting a loop entirely when a goal is reached, or skipping specific iterations while continuing the loop. C++ provides two statements for this: break and continue.

The break statement

The break statement immediately terminates the enclosing loop or switch, transferring execution to the first statement after that construct.

You've already seen break in switch statements, where it prevents fallthrough between cases. But break works with any loop type: while, do-while, and for.

Exiting a loop early

Consider a program that searches for a specific value in user input:

#include <iostream>

int main()
{
    const int targetValue{ 42 };
    bool found{ false };

    std::cout << "I'm thinking of a number. Try to guess it!\n";

    for (int attempts{ 1 }; attempts <= 5; ++attempts)
    {
        std::cout << "Attempt " << attempts << ": ";
        int guess{};
        std::cin >> guess;

        if (guess == targetValue)
        {
            std::cout << "Correct! You found it!\n";
            found = true;
            break;  // no need to continue guessing
        }

        std::cout << "Not quite. Try again.\n";
    }

    if (!found)
        std::cout << "Out of attempts. The number was " << targetValue << ".\n";

    return 0;
}

Sample run:

I'm thinking of a number. Try to guess it!
Attempt 1: 10
Not quite. Try again.
Attempt 2: 42
Correct! You found it!

Without break, the loop would continue asking for guesses even after finding the correct answer. The break statement exits immediately once the goal is achieved.

Infinite loops with exit conditions

Sometimes you don't know in advance how many iterations you'll need. An infinite loop with a break condition handles this elegantly:

#include <iostream>

int main()
{
    std::cout << "Enter words (type 'quit' to stop):\n";

    int wordCount{ 0 };

    while (true)  // runs until break
    {
        std::string word{};
        std::cin >> word;

        if (word == "quit")
            break;

        ++wordCount;
    }

    std::cout << "You entered " << wordCount << " words.\n";

    return 0;
}

Sample run:

Enter words (type 'quit' to stop):
hello world this is a test quit
You entered 6 words.

The loop has no natural termination condition in its header - it relies entirely on break to exit. This pattern is common when the exit condition depends on user input or runtime state.

Break vs return

New programmers sometimes confuse break and return. They're fundamentally different:

  • break exits only the loop (or switch), then continues with code after it
  • return exits the entire function, returning to the caller
#include <iostream>

void searchForValue(int target)
{
    for (int i{ 1 }; i <= 100; ++i)
    {
        if (i == target)
        {
            std::cout << "Found " << target << " at position " << i << '\n';
            break;  // exit loop, continue in function
        }
    }

    // break causes execution to continue here
    std::cout << "Search complete.\n";
}

int findValue(int target)
{
    for (int i{ 1 }; i <= 100; ++i)
    {
        if (i == target)
        {
            return i;  // exit function immediately
        }
    }

    // return skips this entirely
    std::cout << "This never prints if target is found.\n";
    return -1;
}

int main()
{
    std::cout << "Using break:\n";
    searchForValue(50);

    std::cout << "\nUsing return:\n";
    int result{ findValue(50) };
    std::cout << "Returned: " << result << '\n';

    return 0;
}

Output:

Using break:
Found 50 at position 50
Search complete.

Using return:
Returned: 50

Notice that searchForValue prints "Search complete" after the loop because break only exits the loop. In findValue, the final std::cout never executes when the target is found because return exits the entire function.

The continue statement

While break exits a loop entirely, the continue statement skips the rest of the current iteration and jumps to the next one. The loop keeps running - only the current iteration is cut short.

#include <iostream>

int main()
{
    std::cout << "Odd numbers from 1 to 20:\n";

    for (int i{ 1 }; i <= 20; ++i)
    {
        if ((i % 2) == 0)  // if even
            continue;       // skip to next iteration

        std::cout << i << ' ';
    }

    std::cout << '\n';

    return 0;
}

Output:

Odd numbers from 1 to 20:
1 3 5 7 9 11 13 15 17 19

When continue executes, control jumps to the end of the loop body. For a for loop, the end-expression (like ++i) still runs before the next iteration begins.

Continue works differently in while loops

In a for loop, the increment happens after each iteration regardless of how the iteration ended. But in a while loop, if the increment is inside the loop body, continue can skip it - potentially creating an infinite loop:

#include <iostream>

int main()
{
    int i{ 0 };

    while (i < 10)
    {
        if (i == 5)
            continue;  // WARNING: skips the increment!

        std::cout << i << ' ';
        ++i;  // this line is skipped when i == 5
    }

    return 0;
}

This prints 0 1 2 3 4 and then loops forever. When i reaches 5, continue skips the ++i, so i stays 5 indefinitely.

The fix is to increment before the continue:

#include <iostream>

int main()
{
    int i{ 0 };

    while (i < 10)
    {
        ++i;  // increment first

        if (i == 5)
            continue;

        std::cout << i << ' ';
    }

    return 0;
}

Output:

1 2 3 4 6 7 8 9 10

This is one reason to prefer for loops when you have a counter variable - the increment is guaranteed to run.

Using break and continue effectively

Some programmers advise against break and continue because they make control flow less linear. However, when used well, they actually improve readability by reducing nesting and eliminating extra variables.

Reducing nesting with continue

Compare these two approaches to processing only valid data:

// Without continue - deeply nested
for (int i{ 0 }; i < dataSize; ++i)
{
    if (isValid(data[i]))
    {
        if (meetsThreshold(data[i]))
        {
            if (!alreadyProcessed(data[i]))
            {
                processData(data[i]);
            }
        }
    }
}

// With continue - flat structure
for (int i{ 0 }; i < dataSize; ++i)
{
    if (!isValid(data[i]))
        continue;

    if (!meetsThreshold(data[i]))
        continue;

    if (alreadyProcessed(data[i]))
        continue;

    processData(data[i]);
}

The second version reads more naturally: skip invalid data, skip data below threshold, skip already-processed data, then process what remains. Each continue acts as a guard clause, filtering out cases we don't want.

Eliminating flag variables with break

Compare these approaches to finding an element:

// Without break - needs a flag variable
bool found{ false };
int index{ -1 };

for (int i{ 0 }; i < size && !found; ++i)
{
    if (items[i] == target)
    {
        found = true;
        index = i;
    }
}

// With break - clearer intent
int index{ -1 };

for (int i{ 0 }; i < size; ++i)
{
    if (items[i] == target)
    {
        index = i;
        break;
    }
}

The second version eliminates the found variable and the complex loop condition. The intent - find the item and stop looking - is immediately clear.

Best Practice
Use break and continue when they simplify loop logic by reducing nesting or eliminating unnecessary variables.

Early returns

A return statement that appears before the end of a function is called an early return. The same debate applies: some developers prefer a single return at the end for predictability, while others use early returns to exit as soon as the function's work is done.

Early returns are particularly useful for validation at the start of a function:

int processOrder(int quantity, double pricePerUnit)
{
    if (quantity <= 0)
        return -1;  // invalid quantity

    if (pricePerUnit <= 0.0)
        return -2;  // invalid price

    // main logic only runs if inputs are valid
    double total{ quantity * pricePerUnit };
    applyDiscount(total);
    generateInvoice(total);

    return 0;  // success
}

Without early returns, the main logic would need to be wrapped in nested conditionals checking that quantity and price are valid.

Best Practice
Use early returns when they simplify function logic, particularly for input validation.

Summary

  • break immediately exits the enclosing loop or switch and continues with the next statement after it
  • continue skips the rest of the current iteration and begins the next iteration
  • break vs return: break exits only the loop; return exits the entire function
  • In for loops, the end-expression runs after continue (the increment still happens)
  • In while/do-while loops, continue skips everything after it in the body, including any increment statements - this can cause infinite loops if you're not careful
  • Use break to exit a loop early when a goal is reached or an error is detected
  • Use continue to skip unwanted iterations, particularly as guard clauses at the start of loop bodies
  • Both reduce nesting and eliminate flag variables when used appropriately
  • Early returns serve a similar purpose in functions, allowing immediate exit when the function's work is complete or inputs are invalid