In PHP, how do break N and continue N work in nested loops, and why does continue inside a switch behave like break?
answer
- N counts enclosing loops and switches
- literal positive integer only
- switch counts as a loop structure
- continue 2 to reach the outer loop
- compile-time warning names the fix
basics
~20 sbreak N leaves N enclosing loops or switches; continue N jumps to the next iteration of the Nth one. switch counts as a level, so a plain continue inside it acts like break, and PHP warns you to use continue 2.
solid answer
~40 s`break` and `continue` take an optional **level**: `break 2` exits the current structure and the one around it, and `continue 2` jumps to the next iteration of the second enclosing loop. The level must be a **positive integer literal**: a variable is a compile error, as is `0` or a level deeper than the nesting. The surprise is that PHP counts **`switch` as a loop structure** for this purpose. Inside a `switch` inside a `foreach`, a plain `continue` targets the switch and so behaves like `break`, leaving the switch but continuing the rest of the loop body. PHP catches this at compile time with a warning: `"continue" targeting switch is equivalent to "break". Did you mean to use "continue 2"?`. The fix is `continue 2`, or `match` in place of the `switch`.
code
php · 18 lines<?php
declare(strict_types=1);
$shipments = [
['id' => 'PK-1', 'status' => 'cancelled'],
['id' => 'PK-2', 'status' => 'delivered'],
];
foreach ($shipments as $shipment) {
switch ($shipment['status']) {
case 'cancelled':
continue 2; // next shipment
case 'delivered':
echo 'delivered: ';
break; // leave the switch only
}
echo $shipment['id'], PHP_EOL; // prints only "delivered: PK-2"
}go deeper
Know that break leaves a loop or switch and continue skips to the next iteration, and that an optional number selects how many levels.
Explain that switch counts as a level, why a plain continue inside it acts like break, and the compile errors for variable, zero or too-deep levels.
Read the compile warning about continue targeting switch as a real bug signal, and prefer extracting functions over deep numeric levels in production code.
Set review guidance on nested control flow: when numeric break levels are acceptable and when to restructure with functions, match or search helpers.
## What the levels count `break` ends the execution of a structure; `continue` skips the rest of the current iteration and moves on to the next one. Both accept an optional numeric argument saying **how many enclosing structures** to act on: - `break` is the same as `break 1`: leave the innermost `for`, `foreach`, `while`, `do-while` or `switch`. - `break 2` leaves the innermost structure **and** the one around it. - `continue 2` abandons the current iteration of the inner loop, leaves it, and starts the next iteration of the outer loop. The structures that count are the four loop kinds **and `switch`**. An `if` block does not count. ## Rules the compiler enforces The level is checked when the file is compiled, and three mistakes stop compilation: | Code | Result | |---|---| | `break $n;` | compile error: `'break' operator with non-integer operand is no longer supported` | | `break 0;` | compile error: `'break' operator accepts only positive integers` | | `break 3;` inside two loops | compile error: `Cannot 'break' 3 levels` | | `break;` outside any loop | compile error: `'break' not in the 'loop' or 'switch' context` | The same messages exist for `continue`. Variable levels belong to very old PHP, as the "no longer supported" wording says, so any `break $depth` you meet in legacy code must be rewritten. ## Why continue inside a switch acts like break Because `switch` counts as a structure, a `continue` directly inside it **targets the switch**. A switch has no "next iteration", so PHP treats `continue` there as `break`: it leaves the switch and then carries on with the rest of the enclosing loop body. Developers coming from languages where `continue` ignores `switch` expect it to jump to the loop's next iteration, and the statements after the `switch` run unexpectedly. PHP flags this at compile time with an `E_WARNING`: - inside a loop: `"continue" targeting switch is equivalent to "break". Did you mean to use "continue 2"?` - with no loop around the switch: `"continue" targeting switch is equivalent to "break"` The manual says the same thing on the `switch` page: to continue the outer loop, write `continue 2`. ## A parcel-tracker example ```php foreach ($shipments as $shipment) { switch ($shipment['status']) { case 'cancelled': continue 2; // skip this shipment entirely case 'delivered': $delivered++; break; // leave the switch only } notifyCustomer($shipment); // not reached for cancelled shipments } ``` With a plain `continue` in the `cancelled` case, the warning appears and cancelled shipments would still reach `notifyCustomer()`. ## Levels and readability Deep `break 3` statements are legal but hard to follow: a reader has to count braces to know where control lands, and adding one more nesting level silently changes the meaning of every numeric level below it. Common alternatives: 1. **Extract the inner loops into a function** and `return` from it; a `return` needs no counting. 2. **Use `match`** instead of `switch` for value-to-value mapping; `match` is an expression, not a `break`/`continue` target, so a `continue` inside a loop body next to it means the loop. 3. **Use a flag or a search function** such as `array_find()` (PHP 8.4) when the loop exists only to find the first matching element. ## The same rules in every loop kind The level semantics do not depend on the loop kind. In a `for` loop, `continue` still runs the increment expression before re-testing the condition, so `for ($i = 0; $i < 10; $i++) { if (...) continue; }` does not loop forever. In a `while` loop, `continue` jumps straight back to the condition, so a counter updated at the bottom of the body is skipped, a classic cause of an endless `while`. In a `do-while`, `continue` jumps to the condition at the bottom, and the body has already run at least once. ## How this relates to fall-through `break` inside a `switch` also stops fall-through into the next `case`. That is why a `switch` inside a loop mixes two meanings of `break`: "leave this case" and, one level up, "leave the loop". Being explicit with `break 1` versus `break 2` or restructuring with `match` removes that ambiguity.
- What does break 2 do inside a switch that is inside a foreach?It leaves both the `switch` and the `foreach`. The `switch` is level 1 and the loop is level 2, so execution continues with the first statement after the loop. A plain `break` would only leave the `switch` and let the loop body carry on.
- Can the level of break be computed at runtime, for example break $depth?No. The level must be a positive integer literal fixed at compile time. `break $depth` is a compile error saying a non-integer operand is no longer supported. If the exit depth depends on data, restructure the code, typically by extracting a function and returning from it.
Numeric break levels work like floors in a stairwell: break 2 means go down two landings. A switch is a landing too, so from inside it one step down only gets you back into the loop's hallway.
saying these in an interview costs you the question
- Says continue inside a switch jumps to the next iteration of the enclosing loop.
- Believes an if block counts as a level for break N.
- Thinks break $levels with a variable is valid PHP.
- Claims break 0 means break out of every enclosing loop.
- Says switch does not count toward the level of continue N.