0

break, continue, and labeled loops

Beginner5 min read·php-05-005

Concept

break and continue alter loop execution flow. Both accept an optional numeric argument specifying how many levels of nested loops to affect — a PHP feature not present in many other languages.

break exits the loop entirely. break 2 exits two levels of nesting simultaneously, avoiding the need for a flag variable. The argument must be a positive integer literal (not a variable) as of PHP 8.0.

continue skips the remainder of the current loop iteration and proceeds to the next. continue 2 skips to the next iteration of the outer loop.

Labeled loop pattern: PHP doesn't have named labels for loops (unlike Java/Go). The numeric argument to break/continue is the only mechanism to escape nested loops. For deeply nested logic, refactoring into a function where you can return early is usually cleaner.

break in switch: In PHP, a switch inside a loop requires break 2 to exit the surrounding loop, because break with no argument exits the switch — not the loop. This is a notorious gotcha.

Code Example

php
<?php
declare(strict_types=1);

// break — exit current loop
$found = null;
foreach (range(1, 100) as $n) {
    if ($n % 7 === 0 && $n % 13 === 0) {
        $found = $n;
        break; // stop as soon as we find it
    }
}
echo $found; // 91

// continue — skip current iteration
$evens = [];
foreach (range(1, 10) as $n) {
    if ($n % 2 !== 0) continue;
    $evens[] = $n;
}
print_r($evens); // [2,4,6,8,10]

// break 2 — exit nested loops
$matrix = [[1,2,3],[4,5,6],[7,8,9]];
$target = 5;
$found_at = null;
foreach ($matrix as $row => $cols) {
    foreach ($cols as $col => $val) {
        if ($val === $target) {
            $found_at = [$row, $col];
            break 2; // exits both foreach loops
        }
    }
}
echo "Found at row={$found_at[0]}, col={$found_at[1]}"; // row=1, col=1

// continue 2 — skip to next outer iteration
$data = [[1,2,null],[4,5,6],[null,8,9]];
$result = [];
foreach ($data as $row) {
    foreach ($row as $val) {
        if ($val === null) continue 2; // skip this entire row
    }
    $result[] = $row; // only rows with no nulls
}

// The switch/break gotcha
$statuses = [200, 301, 404, 500];
foreach ($statuses as $code) {
    switch ($code) {
        case 200:
            echo "OK\n";
            break; // breaks the SWITCH, not the foreach
        case 404:
            echo "Not found, stopping\n";
            break 2; // breaks the foreach (exits 2 levels: switch + foreach)
    }
}

// Cleaner alternative: refactor into a function
function findFirst(array $matrix, int $target): ?array
{
    foreach ($matrix as $row => $cols) {
        foreach ($cols as $col => $val) {
            if ($val === $target) return [$row, $col];
        }
    }
    return null;
}