Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

Lesson 5 of 6

Sometimes a loop needs to stop before its condition says so, or skip the rest of one pass.

break: leave the loop now

#include <iostream>

int main() {
    int values[] = {4, 8, 15, 16, 23, 42};
    int target = 16;
    int found_at = -1;

    for (int i = 0; i < 6; ++i) {
        if (values[i] == target) {
            found_at = i;
            break;
        }
    }

    std::cout << "found at index " << found_at << "\n";
    return 0;
}
found at index 3

Without break the loop would keep checking indices 4 and 5 pointlessly. On six elements that's nothing; on six million it matters.

You met break in lesson 4.2 ending a switch case. Same keyword, same meaning: leave the enclosing loop or switch.

continue: skip to the next iteration

#include <iostream>

int main() {
    for (int i = 1; i <= 10; ++i) {
        if (i % 3 != 0) {
            continue;
        }
        std::cout << i << " ";
    }
    std::cout << "\n";
    return 0;
}
3 6 9 

continue abandons the rest of the body and jumps to the loop's update step. It's most useful as a guard clause — filtering out cases you don't care about at the top, so the interesting code isn't buried inside nested ifs:

for (const Record& r : records) {
    if (!r.valid) continue;
    if (r.amount == 0) continue;
    // the real work, at one level of indentation
}

Compare that to wrapping everything in if (r.valid && r.amount != 0) { ... }. With three or four conditions, guard clauses win clearly.

One trap: in a while loop, continue jumps to the condition check — skipping any increment you wrote at the bottom of the body:

int i = 0;
while (i < 5) {
    if (i == 2) continue;   // i never changes — infinite loop
    std::cout << i;
    ++i;
}

In a for loop this can't happen, because the update is part of the loop header and always runs. That's one more reason to prefer for when you have a counter.

break only leaves one level

In nested loops, break exits the innermost loop only:

#include <iostream>

int main() {
    for (int row = 0; row < 3; ++row) {
        for (int col = 0; col < 3; ++col) {
            if (col == 1) {
                break;
            }
            std::cout << row << col << " ";
        }
    }
    std::cout << "\n";
    return 0;
}
00 10 20 

The inner loop breaks at col == 1 every time, but the outer loop keeps going. C++ has no break 2;. To leave both, use a flag or — better — put the loops in a function and return:

#include <iostream>

bool contains(int grid[3][3], int target) {
    for (int row = 0; row < 3; ++row) {
        for (int col = 0; col < 3; ++col) {
            if (grid[row][col] == target) {
                return true;      // leaves both loops at once
            }
        }
    }
    return false;
}

int main() {
    int grid[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};

    std::cout << std::boolalpha;
    std::cout << contains(grid, 5) << " " << contains(grid, 99) << "\n";
    return 0;
}
true false

return leaves the entire function regardless of nesting depth. Extracting a nested search into its own function is usually the cleanest answer, and you'll meet functions properly in Module 5.

goto

C++ has goto. It jumps to a label anywhere in the same function:

#include <iostream>

int main() {
    for (int i = 0; i < 3; ++i) {
        for (int j = 0; j < 3; ++j) {
            if (i * j == 2) {
                goto done;
            }
        }
    }

done:
    std::cout << "finished\n";
    return 0;
}
finished

That compiles and works. Don't use it.

The objection isn't superstition. goto destroys the property that makes code readable: that you can understand a block by reading it top to bottom. With arbitrary jumps, understanding any line means knowing every label that could have jumped to it — from anywhere in the function.

The historically legitimate use — breaking out of deeply nested loops, or jumping to cleanup code on error — is better served in C++ by:

  • extracting the loops into a function and using return,
  • RAII (lesson 8.2), which handles cleanup automatically,
  • exceptions (Module 15) for error paths.

You will see goto in C code, especially kernel and embedded code where those alternatives aren't available. Recognise it; don't reach for it.

pass? No such thing

Python programmers look for pass. C++ doesn't need it — an empty block is just {}, and an empty statement is a lone ;:

#include <iostream>

int main() {
    for (int i = 0; i < 3; ++i) {
        // deliberately nothing
    }
    std::cout << "loop finished\n";
    return 0;
}
loop finished

Beware the accidental version: for (int i = 0; i < 3; ++i); — note the semicolon — is a complete loop with an empty body, and the block after it runs once. -Wall warns about it (-Wempty-body).

Try it

Write a program that loops over the numbers 1 to 20 and prints only those divisible by 4, stopping entirely as soon as it finds one greater than 15. Use both continue and break.

Checkpoint

  • break leaves the innermost enclosing loop or switch immediately.
  • continue skips the rest of the body and moves to the next iteration.
  • In a while loop, continue can skip your increment and hang — for is immune.
  • break exits only one level; to leave nested loops, extract them into a function and return.
  • goto exists, works, and should not be used — prefer functions, RAII, and exceptions.

Questions about this lesson

Log in to ask a question.