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.
answer
- Single return exits all enclosing loops
- Extract nested loops into a named function
- firstOrNull / find / any replace search loops
- Labels fine for shallow side-effectful loops
- Deep nesting -> refactor, don't add more labels
basics
~20 sLabels 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 sLabeled `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
Knows labeled break exits an outer loop but may not weigh alternatives.
Mentions extracting a function with return as a cleaner exit.
Compares labels, function extraction with return, and stdlib operators with a clear heuristic for each.
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