skip to content

In Dart, how do labelled break and continue work across nested loops, and where do labels stop working?

level: middleimportance: should knowfreq 30%

answer

  1. a name followed by a colon
  2. terminate versus next iteration
  3. for runs its update, while does not
  4. not across a closure
  5. a helper and return instead

basics

~20 s

A Dart label (name:) before a loop lets break name; end that whole loop and continue name; start its next iteration from inside nested loops; labels cannot be reached from inside a closure, so you cannot break out of forEach.

solid answer

~40 s

You put `outer:` in front of a loop; `break outer;` then terminates that loop and everything nested in it, and `continue outer;` abandons the current inner work and starts the outer loop's next iteration. Unlabelled `break` and `continue` only affect the innermost loop, and `break` inside a `switch` only leaves the switch. In a classic `for` the labelled `continue` still runs the update clause, but in `while` and `do-while` nothing advances for you, so you must increment before continuing or loop forever. Labels do not cross function boundaries, which is why you cannot break out of a `forEach` callback. When nesting gets deep, a helper function with `return`, or `any`/`firstWhere`, is usually clearer.

code

dart · 41 lines
dart
class Bracket {
  const Bracket(this.upTo, this.rate);
  final double upTo;
  final double rate;
}

const brackets = [
  Bracket(10000, 0.10),
  Bracket(40000, 0.20),
  Bracket(double.infinity, 0.35),
];

/// Tax per payer; a payer with any negative income line is skipped.
Map<String, double> taxAll(Map<String, List<double>> incomeLines) {
  final result = <String, double>{};
  payers:
  for (final name in incomeLines.keys) {
    var income = 0.0;
    for (final line in incomeLines[name]!) {
      if (line < 0) continue payers; // skip this payer entirely
      income += line;
    }
    var tax = 0.0;
    var lower = 0.0;
    for (final bracket in brackets) {
      if (income <= lower) break; // leaves the bracket loop only
      final top = income < bracket.upTo ? income : bracket.upTo;
      tax += (top - lower) * bracket.rate;
      lower = bracket.upTo;
    }
    result[name] = tax;
  }
  return result;
}

void main() {
  print(taxAll({
    'ana': [30000, 5000],
    'bo': [12000, -1],
  })); // {ana: 6000.0}
}

go deeper

for a junior

Recall the syntax: name: before the loop, then break name; or continue name; from inside a nested loop.

for a middle

Explain the difference between terminating and continuing a labelled loop, what the update clause does, and why labels cannot reach into a closure.

for a senior

Judge readability: when a labelled continue is the clearest expression of two different exits, and when to refactor into a helper with return or an any() pre-check.

for a principal

Treat nested control flow as a maintainability signal in review: deep label use often points at a missing abstraction or validation step.

## What a label is A **label** is an identifier followed by a colon, `labelName:`, placed in front of a statement. Loops and switch cases are the usual targets. A label changes nothing by itself; it gives `break` and `continue` a name to aim at: - **`break labelName;`** terminates the labelled statement. On an outer loop, that ends the outer loop and every loop nested inside it. - **`continue labelName;`** skips the rest of the current iteration of the labelled loop and starts its next iteration. Without a label, `break` and `continue` act on the **innermost** enclosing loop, and a `break` inside a `switch` ends just that switch. ## A worked scenario: tax brackets for several payers A tax calculator walks every payer, sums their income lines, then walks the brackets. Two different exits are needed: 1. If a payer has an invalid (negative) income line, **skip that payer entirely**, which means leaving the inner loop and moving to the next payer: `continue payers;`. 2. While applying brackets, once the income is used up, **stop the bracket loop only**: a plain `break;`. ```dart payers: for (final name in incomeLines.keys) { var income = 0.0; for (final line in incomeLines[name]!) { if (line < 0) continue payers; // skip the whole payer income += line; } // ... bracket loop with a plain break ... } ``` The labelled `continue` skips the rest of that payer's work, including the line that would store a result. ## How the three loop kinds behave with `continue label` | Loop | What `continue outer;` does next | |---|---| | `for (init; cond; update)` | runs the outer **update** clause, then tests the condition | | `for (x in iterable)` | moves to the next element | | `while (cond)` | tests the condition directly; nothing advances the counter | | `do { } while (cond)` | tests the condition at the bottom | The `while` and `do-while` rows are the trap. The dart.dev examples increment the outer counter by hand **before** `continue outerLoop;`; forget that and the loop spins on the same value forever. ## The rules the analyzer enforces - **Labels do not cross function boundaries.** A `break` inside a closure cannot target a loop outside it; the analyzer reports that it can't reference a label declared in an outer method. That is one reason you cannot break out of `list.forEach((x) { ... })`: the body is a separate function. - **`continue` must target a loop or a switch member**, never an arbitrary statement. - **An unused label** produces the `unused_label` warning. - **A `break` inside a `switch`** exits the switch, not an enclosing loop. To leave the loop from a case, label the loop and `break` to it. ## When a label is the right tool, and when it is not Labels are fine for two or three levels of nesting where the exits are genuinely different, as in the tax example. They become hard to read when: - several labels are in play at once; - the labelled `break` is far from the label; - the same effect is a single `return` from a small helper. Alternatives worth naming: 1. **Extract a function** and `return` from it: often the clearest exit from nested loops. 2. **Use `Iterable` methods** such as `any`, `every` or `firstWhere` for searches, which stop early by design. 3. **Validate first**: run a quick `any((line) => line < 0)` check before the heavy loop, so the inner `continue payers` disappears. ## What an interviewer listens for - The syntax `label:` plus `break label;` or `continue label;`, with the difference between the two. - That unlabelled forms affect only the innermost loop, and `break` in a switch only the switch. - The `while` loop pitfall of not advancing before `continue`. - The closure boundary, and when a helper function beats a label.

  • Why does continue outer; in a Dart while loop sometimes hang the program?
    A `while` loop has no update clause, so `continue outer;` jumps straight back to the outer condition. If the counter is only incremented at the bottom of the outer body, that increment is skipped and the same value is tested again forever. Increment before the `continue`, as the dart.dev examples do, or switch to a `for` loop, whose update clause runs on `continue`.
  • Can you use a labelled break to stop a Dart forEach callback early?
    No. The callback is a separate function, and labels do not cross function boundaries; the analyzer rejects a reference to a label declared in an outer method. `return` inside the callback only ends that one call. Use a `for-in` loop, where `break` works, or a method that stops early such as `any` or `firstWhere`.

saying these in an interview costs you the question

  • Thinks an unlabelled break exits every nested loop
  • Believes continue label restarts the outer loop from the start
  • Expects a while loop to advance its counter on continue
  • Tries to break out of forEach with a label
  • Says Dart has no labels, only return