break, continue and Labels
Mental model: break leaves the loop, continue skips to the next pass, and a label says which loop you meant.
Level: beginner · about 9 minutes
for (const n of [1, 2, 3, 4, 5]) {
if (n === 2) continue; // skip the rest of THIS pass
if (n === 4) break; // leave the loop entirely
console.log(n); // → 1, then 3
}
console.log('done');Two keywords, two very different jobs.
continuejumps to the update step and starts the next pass. Nothing after it in the body runs.breakabandons the loop and continues with the statement after it.- Both apply to the innermost enclosing loop, which is exactly the problem labels solve.
const rows = [
{ id: 1, price: 10, inStock: true },
{ id: 2, price: 99, inStock: false },
{ id: 3, price: 5, inStock: true },
];
let total = 0;
for (const row of rows) {
if (!row.inStock) continue; // one reason to skip, on its own line
total += row.price;
}
console.log(total); // → 15A guard clause inside a loop reads like a guard clause inside a function.
Labels: naming a loop so you can leave it
A plain break inside a nested loop only escapes the inner one, so the outer loop carries on searching after you already found what you wanted. A label is an identifier followed by a colon in front of a statement, and break label leaves that statement.
const grid = [[1, 2], [3, 4], [5, 6]];
search: for (const row of grid) {
for (const cell of row) {
if (cell === 4) {
console.log('found 4');
break search; // leaves BOTH loops
}
console.log('checked', cell);
}
}
console.log('after the loops');
Without a label: a flag
let found = false;
for (const row of grid) {
for (const cell of row) {
if (cell === 4) { found = true; break; }
}
if (found) break;
}
With a label: no flag
search: for (const row of grid) {
for (const cell of row) {
if (cell === 4) break search;
}
}continue label also exists and means "start the next pass of the labelled loop". Labels are rare enough that a reader will slow down when they see one, so keep the name descriptive: search, outer, rows.
function findCell(grid, wanted) {
for (const [r, row] of grid.entries()) {
for (const [c, cell] of row.entries()) {
if (cell === wanted) return [r, c]; // no label needed
}
}
return null; // say "not found" out loud
}
console.log(findCell([[1, 2], [3, 4]], 4)); // → [1, 1]
console.log(findCell([[1]], 9)); // → null
some and every are early exits with names
const nums = [1, 3, 6, 7, 9];
const checked = [];
const hasEven = nums.some((n) => {
checked.push(n);
return n % 2 === 0;
});
console.log(hasEven); // → true
console.log(checked); // → [1, 3, 6] it stopped as soon as it knew
| Question | Method | Stops as soon as |
|---|---|---|
| Is at least one true? | some | the callback returns truthy |
| Are all of them true? | every | the callback returns falsy |
| Which element matches? | find | the callback returns truthy |
| Where is it? | findIndex | the callback returns truthy |
| Is this exact value present? | includes | a strict match is found |
let output = '';
for (let i = 0; i < 5; i++) {
if (i % 2 === 0) continue;
if (i > 3) break;
output += i;
}
console.log(output);Even values are skipped by continue, so only 1 and 3 reach the bottom of the body. When i is 5 the loop condition has already failed, so the break at i > 3 never fires. The result is the string '13', built by += on a string.
Try it yourself
Escape the nested loop three ways
const grid = [
['a', 'b', 'c'],
['d', 'e', 'f'],
['g', 'h', 'i'],
];
let visited = 0;
outer: for (const row of grid) {
for (const cell of row) {
visited += 1;
if (cell === 'e') break outer;
}
}
console.log('labelled break visited', visited, 'cells');
visited = 0;
let found = false;
for (const row of grid) {
for (const cell of row) {
visited += 1;
if (cell === 'e') { found = true; break; }
}
if (found) break;
}
console.log('flag version visited', visited, 'cells');
Comment out the labelled version and make the flag version behave identically. Then rewrite it as a function with a return and compare the line counts.
Exercises
Sum the even numbers with continue
Write sumEven(numbers) which adds up only the even values. Use a continue guard for the odd ones rather than wrapping the addition in an if, so the loop body stays flat.
Find the first pair with a labelled break
Write findFirstPair(left, right, target). Walk left in the outer loop and right in the inner loop, and return [x, y] for the first pair whose sum equals target. Return null when nothing matches. Use a labelled break so the outer loop stops as soon as you have an answer.
Check yourself
- What does this log?
- [1] —
1is pushed.2is skipped bycontinue.3ends the loop before the push, so4is never reached. Only[1]survives. - A plain
breakinside two nested loops leaves… - the innermost loop only —
breakalways applies to the nearest enclosing loop orswitch. To leave an outer loop you either label it and usebreak label, keep a flag, or extract the loops into a function andreturn. - Why does
someoften replace aforloop with abreak? - It short circuits on the first truthy result, so it does the same work and reads as a question —
somestops calling the callback as soon as one returns truthy, exactly like abreak. The gain is intent: the code now says "is there at least one" instead of describing the mechanics.
Common mistakes
- Using
continuein awhileloop above the increment, which makes the loop run forever. - Expecting a plain
breakin a nested loop to leave both loops. - Reaching for
forEachand then discovering there is no way to stop it.
Takeaways
continueskips the rest of the current pass;breakleaves the loop.- Both target the innermost loop unless you label the one you mean.
- Extracting the loops into a function and returning is usually cleaner than either a label or a flag.
some,every,findandfindIndexare early exits with intent baked into the name.