In a Dart 3 switch statement, do cases fall through, and when do you still write break or continue inside a case?
answer
- non-empty versus empty case bodies
- jump to the end of the switch
- old Dart made break mandatory
- a label on the target case
- break leaves the switch, not the loop
basics
~20 sDart 3 switch statements never fall through from a non-empty case: its body runs and control leaves the switch, so no break is needed. Empty cases share the next body, and continue with a label jumps into another case.
solid answer
~40 sIn Dart 3, a non-empty `case` body completes and jumps to the end of the `switch`; `break` is optional and the `unnecessary_breaks` lint flags a trailing one. An empty case falls through, so `case 'DENIED': case 'CLOSED': closeIt();` runs `closeIt()` for both. Before Dart 3.0 a non-empty case had to end with `break`, `continue`, `return` or `throw`, or it was a compile error, so Dart has never silently fallen through like C. You still write `break` as the body of an empty case that must do nothing, and `continue someLabel;` when you deliberately want another labelled case's body to run. Inside a loop, an unlabelled `break` in a case only exits the switch.
code
dart · 20 linesenum Filing { single, joint, headOfHousehold, exempt }
double standardDeduction(Filing filing) {
var deduction = 0.0;
switch (filing) {
case Filing.single:
deduction = 14000;
case Filing.joint:
deduction = 28000;
case Filing.headOfHousehold:
deduction = 21000;
case Filing.exempt:
break; // an empty body that must do nothing
}
return deduction;
}
void main() {
print(standardDeduction(Filing.single)); // 14000.0, no fall-through
}go deeper
Recall that a non-empty case runs and leaves the switch without needing break, and that empty cases share the next body.
Explain the Dart 3.0 change: break was mandatory, never implicit fall-through, and now the jump is automatic. Show continue with a labelled case for explicit fall-through.
Spot the migration and loop traps: redundant breaks left after an upgrade, and a break in a case that was meant to leave the surrounding loop.
Decide team conventions: when a labelled continue is acceptable versus a shared helper, and whether to enable unnecessary_breaks once every package is on language 3.0.
## The rule in one line In a Dart 3 `switch` **statement**, a **non-empty** `case` body runs and then control jumps to the end of the switch. There is no implicit fall-through into the next case, and no `break` is needed. An **empty** case, one with no statements, falls through to the next case so that several cases can share one body. ## How it looks ```dart switch (command) { case 'OPEN': executeOpen(); // runs, then leaves the switch case 'DENIED': // empty: falls through case 'CLOSED': executeClosed(); // runs for DENIED and CLOSED default: executeUnknown(); // runs when no case matches } ``` - **`default`** or the wildcard **`_`** handles values no case matched. - A non-empty case may also end with `continue`, `throw` or `return`; `break` still works but is redundant at the end of a body. The `unnecessary_breaks` lint flags that leftover `break`. - An **empty case that should do nothing** (rather than fall through) gets `break;` as its only statement. ## What changed in Dart 3.0, and what did not | | Before Dart 3.0 | Dart 3.0 and later | |---|---|---| | Non-empty case without `break` | compile error (`switch_case_completes_normally`) | allowed; jumps to the end | | Implicit fall-through from a non-empty case | never | never | | Empty case | falls through | falls through | | What a `case` holds | a constant expression | a **pattern** (constants still work) | The important point for an interview: Dart has **never** silently fallen through from a non-empty case. Before 3.0 the language forced you to write `break` (or `continue`, `return`, `throw`) and reported an error otherwise; Dart 3.0 simply made the `break` implicit. Code ported from a C-family language that relies on fall-through does not compile in old Dart and does not fall through in new Dart. ## Deliberate fall-through: `continue` with a label When one case really should run another case's body afterwards, you say so explicitly. Put a **label** on the target case and end the source case with `continue label;`: ```dart switch (command) { case 'OPEN': executeOpen(); continue closed; // jump into the CLOSED body closed: case 'CLOSED': executeClosed(); } ``` This is rare in practice, and reviewers usually prefer a shared helper function, but it is the only way to get a non-sequential or multi-body flow in a switch statement. ## `break` inside a switch inside a loop Inside a `switch`, an unlabelled `break` ends the **switch**, not an enclosing loop. That surprises developers who expect `break` to leave the loop: 1. The `break` exits the switch. 2. The loop continues with its next iteration. 3. To leave the loop from inside a case, put a label on the loop and write `break loopLabel;`, or `return` from the function. ## Related rules you should mention but not confuse - **Cases are patterns** since 3.0, so a case can be a constant, a range with relational patterns or several alternatives joined with `||`. Pattern syntax itself is its own topic. - **Switch statements on enums, sealed types and `bool` are checked for exhaustiveness**: the analyzer reports `non_exhaustive_switch_statement` if a value could slip through with no matching case. Adding a `default` silences the check for future enum values too. - **Switch expressions** (`final label = switch (x) { ... };`) are a different construct with their own rules: no empty cases, no fall-through at all, `=>` bodies. ## What an interviewer listens for - "Non-empty cases don't fall through and don't need `break` in Dart 3." - "Empty cases share the next body." - "Old Dart required `break`; it was never a silent fall-through." - "`continue label` is the explicit fall-through; `break` in a switch only leaves the switch."
- How do you make one Dart switch case run another case's body after its own?Label the target case and end the source case with `continue label;`, for example `closed:` before `case 'CLOSED':` and `continue closed;` at the end of the OPEN body. Control then jumps into the CLOSED body. It is explicit by design; most reviewers would still prefer extracting the shared work into a function.
- Inside a for loop, what does an unlabelled break inside a Dart switch case exit?Only the `switch`. The loop carries on with its next iteration. To stop the loop from inside a case, label the loop (`outer: for (...)`) and write `break outer;`, or return from the enclosing function.
- Why was old Dart code full of break statements if Dart never fell through?Before Dart 3.0 the language required every non-empty case to end in `break`, `continue`, `return` or `throw`; omitting it was a compile error rather than a fall-through. Dart 3.0 made the jump to the end implicit, so those `break` statements are now redundant, and the `unnecessary_breaks` lint can find them.
saying these in an interview costs you the question
- Says Dart 3 cases fall through without break, as in C
- Believes older Dart silently fell through when break was missing
- Thinks an empty case is a compile error
- Expects break inside a switch to exit the enclosing loop
- Reaches for a fallthrough keyword that Dart does not have