Free lesson · JavaScript Basic to Advance
break, continue, and Loop Control
Controlling Loop Flow
Loops run until their condition becomes false. But sometimes you need finer control — exit early, skip an iteration, or jump out of a nested loop. JavaScript gives you three tools for this: break, continue, and labeled statements.
// Stop the loop entirely
for (let i = 0; i < 10; i++) {
if (i === 5) {
break;
}
console.log(i);
}
// Prints: 0, 1, 2, 3, 4
// Skip just this iteration
for (let i = 0; i < 10; i++) {
if (i % 2 === 0) {
continue;
}
console.log(i);
}
// Prints: 1, 3, 5, 7, 9
These tools are simple in isolation. The interesting part is when to reach for them and when to restructure your code so you don't need them.
Interactive animation: continue and break in one walk over an array - skip one element, then abandon the rest.
break: Exit the Loop
The break keyword exits the loop immediately, skipping any remaining iterations.
const numbers = [1, 5, 3, 8, 2, 9, 4];
// Find the first number greater than 6
let found = null;
for (const num of numbers) {
if (num > 6) {
found = num;
break; // we have what we need, stop looking
}
}
console.log(found); // 8
break is the right tool when you've found what you came for and there's no reason to keep iterating. The classic case is searching — once you find the target, you stop.
break also works inside while and do...while loops. It does not work inside forEach, map, filter, or reduce — those methods are designed to visit every element and have no notion of early exit. That's the limitation we covered in Topic 4.4.
continue: Skip This Iteration
The continue keyword skips the rest of the current iteration and jumps to the next one. The loop doesn't stop — it just moves on.
const numbers = [1, 2, 3, 4, 5, 6, 7, 8];
for (const num of numbers) {
if (num % 2 === 0) {
continue; // skip even numbers
}
console.log(`Processing ${num}`);
}
// Prints: Processing 1, Processing 3, Processing 5, Processing 7
continue is useful when you want to filter out certain iterations within a loop. It's an alternative to wrapping the rest of the loop body in an if block.
Compare these two styles:
const items = [1, 2, 3, 4, 5];
// Using continue
for (const item of items) {
if (item < 3) {
continue;
}
console.log(`Item: ${item}`);
}
// Using if (no continue)
for (const item of items) {
if (item >= 3) {
console.log(`Item: ${item}`);
}
}
Both produce the same output. Which one reads more clearly depends on how much code is inside the loop body. For a single line, the if version is cleaner. For longer loop bodies with multiple conditions to skip, continue keeps the main logic from being deeply nested.
graph TD
A[Start iteration] --> B[Run loop body]
B --> C{Hit break?}
C -->|Yes| D[Exit loop entirely]
C -->|No| E{Hit continue?}
E -->|Yes| F[Skip rest of this iteration]
F --> G[Go to next iteration]
E -->|No| H[Continue normally]
H --> G
style D fill:#dc2626,color:#fff
style F fill:#f59e0b,color:#fff
style H fill:#10b981,color:#fff
This diagram shows the control flow when break and continue are encountered inside a loop. As each iteration runs, if it hits a break statement, the entire loop exits immediately. If it hits a continue statement, the rest of the current iteration is skipped and execution jumps to the next iteration. If neither is hit, the iteration completes normally and moves on.
Nested Loops
When you have a loop inside another loop, break and continue only affect the innermost loop they appear in. This is important when working with nested iterations.
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
for (const row of matrix) {
for (const value of row) {
if (value === 5) {
break; // only breaks the inner loop
}
console.log(value);
}
}
// Prints: 1, 2, 3, 4, 7, 8, 9
// The 5 was hit, broke the inner loop, then the outer loop continued with the next row
When the inner loop breaks at 5, the outer loop just moves on to the next row. We never see 5 (because of the break) but we keep iterating over the rest of the matrix.
This is usually what you want. Occasionally, you need to break out of all the loops at once — and for that, JavaScript has labeled statements.
Interactive animation: Escaping nested loops - a flag variable and two breaks, against one labeled break that exits both.
Labeled Statements (Rare but Useful)
A label is a name you give to a loop. You can then break or continue to that specific label, skipping out of inner loops to the labeled one.
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
outer: for (const row of matrix) {
for (const value of row) {
if (value === 5) {
break outer; // break out of BOTH loops
}
console.log(value);
}
}
// Prints: 1, 2, 3, 4
// Found 5, broke all the way out
The label outer: names the outer loop. break outer jumps out of that loop entirely, no matter how deeply nested you are inside it.
Labeled statements are rare in real code. Many style guides discourage them because they make control flow harder to follow. The common alternative is to extract the nested loops into a function and use return to exit:
function findValue(matrix, target) {
for (const row of matrix) {
for (const value of row) {
if (value === target) {
return value; // exits the function, naturally bypassing all loops
}
}
}
return null;
}
const matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
console.log(findValue(matrix, 5));
This is the modern idiomatic way to handle "break out of nested loops." The function approach is clearer than labels in almost every case. Labels exist for the rare situation where you genuinely can't restructure into a function, but reach for return first.
When break and continue Are Anti-Patterns
break and continue are powerful, but they can hide control flow. A long loop with multiple break and continue statements scattered through it is hard to follow. The logic of "when does this loop actually run all the way through, and when does it exit early?" becomes a puzzle.
Two refactors that often help:
Replace early breaks with for...of and search methods. If your loop is just looking for an element, use find, findIndex, or some instead:
const numbers = [1, 5, 3, 8, 2];
// Instead of: loop with break
let found = null;
for (const n of numbers) {
if (n > 6) {
found = n;
break;
}
}
// Use: find()
const foundCleaner = numbers.find(n => n > 6);
console.log(foundCleaner); // 8
Replace continue with filter and forEach. If your loop is just skipping certain elements, filter the array first:
const numbers = [1, 2, 3, 4, 5];
// Instead of: loop with continue
for (const n of numbers) {
if (n % 2 === 0) continue;
console.log(`Odd: ${n}`);
}
// Use: filter then forEach (or for...of)
numbers
.filter(n => n % 2 !== 0)
.forEach(n => console.log(`Odd: ${n}`));
These refactors aren't always better. Sometimes break and continue are the right tool. But when your loop body is getting complicated, restructuring with the right array method often produces cleaner code.
Interactive animation: The early-exit methods like find, findIndex and some - what each returns and where it stops.
Common Pitfalls
Warning: The traps with break and continue.
Trying to break out of
forEach. Doesn't work.forEachhas no break. Usefor...ofinstead, or restructure withfindorsome.Confusing
breakandreturn.breakexits a loop.returnexits a function. If you're inside a function and want to stop entirely, usereturn. If you're just inside a loop and want to stop the loop, usebreak.Overusing labels. Labeled break/continue is hard to read. Almost any code using labels can be restructured into a function with
returnfor clarity. Reach for that first.
Why This Matters in Interviews
Interview: Loop control is rarely a direct interview topic, but it shows up in code judgment. Interviewers notice when a candidate writes a complex loop with three early breaks when a cleaner solution exists. They also notice when a candidate doesn't know about
find,some,every— modern array methods that often replace complex loop control logic.Topic 4.6 — Interview Lens: Control Flow and Iteration Patterns — covers the broader interview treatment of these decisions, including the patterns interviewers value in well-structured solutions.
Try It
Exercise: Refactor this code into something cleaner using modern array methods. The original works, but it has nested loops with
breakandcontinuethat obscure what's actually happening:function findFirstPair(numbers, target) { for (let i = 0; i < numbers.length; i++) { if (numbers[i] <= 0) continue; for (let j = i + 1; j < numbers.length; j++) { if (numbers[j] <= 0) continue; if (numbers[i] + numbers[j] === target) { return [numbers[i], numbers[j]]; } } } return null; }Try at least two different refactors. The cleaner version may not use
breakorcontinueat all. Compare your versions — which one would you want to maintain six months from now?