break and continue
break and continue are the two ways to short-circuit a loop. break ends the loop entirely; continue skips to the next iteration. They are simple, well-known, and slightly more nuanced than they look — especially around nested loops, switch blocks, and the loops that can't be broken out of.
break: stop the loop
function firstEven(numbers) {
for (const n of numbers) {
if (n % 2 === 0) {
return n;
}
}
return undefined;
}
// or without return:
function indexOfFirstEven(numbers) {
let found = -1;
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] % 2 === 0) {
found = i;
break;
}
}
return found;
}Once break runs, control jumps to the statement after the loop. Anything queued up below in the loop body is skipped.
continue: skip this iteration
continue jumps straight to the next iteration. In a for loop, the update step still runs. In a while, the condition is checked next.
for (let i = 0; i < 10; i++) {
if (i % 2 === 0) continue; // skip evens
console.log(i);
}1 3 5 7 9
Both inside one loop
for (const line of lines) {
if (line.startsWith("#")) continue; // skip comments
if (line === "END") break; // stop at sentinel
process(line);
}The pattern reads almost as English: "for each line, skip comments, stop at the sentinel, otherwise process". This is the kind of place loops still beat array methods on clarity.
Inside nested loops
A plain break only exits the innermost enclosing loop. The outer loop continues.
for (let r = 0; r < grid.length; r++) {
for (let c = 0; c < grid[r].length; c++) {
if (grid[r][c] === target) {
console.log("found at", r, c);
break; // exits the inner loop only
}
}
}Three idiomatic ways to escape the outer loop too:
Pull the inner loop into a function and
return— cleanest in most cases.Set a flag and check it at the top of the outer loop.
Use a labelled
break— see the Labels page.
The function-and-return version
function find(grid, target) {
for (let r = 0; r < grid.length; r++) {
for (let c = 0; c < grid[r].length; c++) {
if (grid[r][c] === target) return [r, c];
}
}
return null;
}Inside switch — break vs return
break inside a switch exits the switch, not any surrounding loop. That makes it noisy when a switch is nested in a loop: a "loop break" looks identical to a "switch break".
for (const event of events) {
switch (event.type) {
case "STOP":
// break here only exits the switch — the loop keeps going.
// To exit the loop you need a labelled break or a flag.
break;
default:
handle(event);
}
}Where they don't work: forEach
Array.prototype.forEach, map, filter, reduce etc. accept a callback — and you cannot break out of a callback. continue and break would throw "Illegal break/continue statement".
This does NOT work
[1, 2, 3, 4].forEach(n => {
if (n === 3) break; // SyntaxError
console.log(n);
});The alternatives:
some()— returnstrueand stops the moment the callback returns truthy. Use when you want "stop as soon as I find one".every()— stops on the first falsy callback. Use for "are they all valid?".for...of— supportsbreakandcontinuenatively. Use when you want imperative control.returninforEach's callback acts likecontinue, notbreak. The loop still runs to the end.
some() = forEach with early exit
const hasNegative = numbers.some(n => n < 0); // stops at first negative
continue and the update step
Subtle but important: in a classic for (init; cond; update) loop, continue runs the update before re-checking the condition. In a while, it goes straight back to the condition. Forgetting that is how continue produces an accidental infinite loop:
Infinite loop trap
let i = 0;
while (i < 10) {
if (i === 5) continue; // never increments i past 5
console.log(i);
i++;
}The fix is to increment before the continue, or to switch to a for loop where the update is in the header.