skip to content

Labeled break/continue can express any nested-loop exit, but is it always the best choice? Discuss the trade-offs and the main alternative for escaping nested loops in Kotlin.

level: seniorimportance: should knowfreq 30%

answer

  1. Single return exits all enclosing loops
  2. Extract nested loops into a named function
  3. firstOrNull / find / any replace search loops
  4. Labels fine for shallow side-effectful loops
  5. Deep nesting -> refactor, don't add more labels

basics

~20 s

Labels work, but deeply nested labeled jumps get hard to read. Often it's cleaner to pull the nested loops into their own function and use a normal return to exit everything at once, or use a collection operation like firstOrNull.

solid answer

~50 s

Labeled `break@`/`continue@` are correct and idiomatic for shallow nesting, but they don't scale gracefully: multiple labels in deeply nested loops obscure control flow. The strongest alternative is to **extract the nested loops into a function** and use a plain `return` (optionally `return value`) — a single `return` cleanly exits all enclosing loops because it leaves the function, and it often lets you name the operation (`findFirstMatch(...)`). A second alternative is to replace imperative search loops with the standard library: `firstOrNull { }`, `any { }`, `find { }`, or a `for` over a flattened sequence, which removes the need for any jump. Use labeled jumps when the loop has meaningful side effects per iteration and extraction would be artificial; prefer extraction/return or functional operators when the loop is essentially a search or accumulation. The key principle: labeled break is a tool for explicit outer-loop exit, not a default — readability and single-responsibility usually favor a named function with early return.

go deeper

for a junior

Knows labeled break exits an outer loop but may not weigh alternatives.

for a middle

Mentions extracting a function with return as a cleaner exit.

for a senior

Compares labels, function extraction with return, and stdlib operators with a clear heuristic for each.

for a principal

Ties the choice to readability, testability, and single-responsibility, and judges when labeled control flow is a design smell.

## The three ways to escape nested loops ### 1. Labeled break/continue ```kotlin outer@ for (row in grid) { for (cell in row) { if (cell == target) { found = cell break@outer } } } ``` Explicit and local, but as nesting deepens the labels multiply and readers must track where each `@label` points. ### 2. Extract a function + `return` A single `return` exits **all** enclosing loops because it returns from the whole function: ```kotlin fun findTarget(grid: List<List<Cell>>, target: Cell): Cell? { for (row in grid) { for (cell in row) { if (cell == target) return cell // exits both loops at once } } return null } ``` Benefits: no labels, the operation gets a name, the result type is explicit, and it composes/tests well. This is the most common senior recommendation when the nested loop is a search. ### 3. Standard-library operators Many nested-loop searches collapse into declarative calls: ```kotlin val cell = grid.firstNotNullOfOrNull { row -> row.firstOrNull { it == target } } // or val exists = grid.any { row -> row.any { it == target } } ``` These remove jumps entirely and read as intent. `find`, `firstOrNull`, `any`, `none`, `firstNotNullOfOrNull` are the workhorses. Note these use lambdas, where escaping early uses labeled `return@` (a different mechanism), but you usually don't need it because the operator stops on first match itself. ## When labeled jumps are still right - The inner work has **side effects** on each iteration (logging, mutation) that shouldn't be hidden inside a returned helper. - You need `continue@outer` semantics that aren't naturally a search. - Extraction would create an awkward, single-use function with many captured parameters. ## Decision heuristic | Situation | Prefer | |-----------|--------| | Pure search/first-match | `firstOrNull`/`find`/`any` | | Search with a meaningful name/result | extract function + `return` | | Side-effectful loop, shallow nesting | labeled break/continue | | Deeply nested with several exit points | extract + return; reconsider design | The meta-point for senior candidates: labeled break is never *wrong*, but choosing it over extraction signals whether you optimize for explicit local control or for readable, testable units.

  • Why does a plain `return` escape nested loops while `break` does not?
    return exits the entire function (all enclosing loops included); break only exits one loop. That's why an extracted function with return is a clean nested-loop exit.
  • If you replace the loop with `firstOrNull`, do you still need any label?
    No — the operator stops at the first match itself; you neither break nor use return@ for the common case.

Labeled break is a fire-escape on each floor; extracting a function with return is just leaving the building through the front door in one move.

saying these in an interview costs you the question

  • Insisting labeled break is always the most readable option
  • Not knowing return from an extracted function escapes all loops
  • Unaware of firstOrNull/find/any as loop replacements
  • Confusing return@lambda with break@loop when discussing alternatives
  • Reaching for boolean flags instead of either labels or extraction

context