A teammate writes a 30-line map lambda with several return@map early-exits and intermediate vals. As a reviewer, what concerns and alternatives would you raise about multi-statement-body returns?
answer
- Long return-heavy lambda = readability smell
- Extract to named fun + ::reference
- Anonymous fun = natural local return
- when/mapNotNull/filter over early exits
- Verify element type, beware List<Unit>
basics
~20 sLong lambdas with many early exits are hard to read and test. Suggest extracting the body into a named function, or using an anonymous function with plain returns, so the transformation logic is clear and reusable.
solid answer
~40 sBig multi-statement lambdas with several `return@map` exits hide control flow and resist unit testing. I'd raise: (1) **extract** the body into a private named function and call `list.map(::transform)` — now it has a name, a tested signature, and a clear return type; (2) if early returns read awkwardly with labels, use an **anonymous function** (`fun(x): R { ... return ... }`) so a plain `return` is local and reads naturally; (3) watch for the **List<Unit> trap** if the last expression isn't the intended value; (4) consider whether `mapNotNull`, `filter` + `map`, or `when` expressions express the branching more declaratively than imperative early returns. The goal is that the lambda's result type and exit points are obvious at a glance, and that the logic is independently testable.
code
kotlin · 11 lines// Before: hard-to-test inline early exits
val rows = items.map { i ->
if (i.hidden) return@map Row.EMPTY
val cells = i.fields.map(::toCell)
Row(cells)
}
// After: extracted, testable
private fun toRow(i: Item): Row =
if (i.hidden) Row.EMPTY else Row(i.fields.map(::toCell))
val rows2 = items.map(::toRow)go deeper
Recognizes the lambda is long but may only suggest comments.
Suggests extracting a named function and verifying the result type.
Weighs anonymous functions, mapNotNull/filter/when, and testability tradeoffs deliberately.
Codifies team guidance on lambda size, early-return style, and inference safety, balancing readability against performance/inlining.
## Why long return-heavy lambdas are a smell A lambda's value comes from its **last expression** or a **qualified `return@map`**. When a lambda grows to many statements with several `return@map` early exits, readers must trace every branch to know what each element maps to, and the logic can't be unit-tested in isolation. Multi-statement bodies are fine; *large* ones with scattered exits are the problem. ## Alternatives ### 1. Extract a named function (usually best) ```kotlin private fun toRow(item: Item): Row { if (item.hidden) return Row.EMPTY val cells = item.fields.map(::toCell) return Row(cells) } val rows = items.map(::toRow) ``` Now there's a named, typed, testable unit; `::toRow` is a **function reference**. Plain `return` inside the function is local and natural. ### 2. Anonymous function for natural local returns ```kotlin val rows = items.map(fun(item: Item): Row { if (item.hidden) return Row.EMPTY // local return, no @label return Row(item.fields.map(::toCell)) }) ``` This avoids `return@map` noise while keeping the logic inline. ### 3. More declarative operators - `mapNotNull { ... }` instead of mapping some elements to a sentinel. - `filter { }.map { }` to separate selection from transformation. - A `when` expression as the single last expression instead of several `return@map`s: ```kotlin items.map { item -> when { item.hidden -> Row.EMPTY else -> Row(item.fields.map(::toCell)) } } ``` A `when` used as an expression makes the single result obvious — no early returns at all. ## Correctness checks - Confirm the **last expression** (or each `return@map`) yields the intended element type; a stray Unit-returning last line silently produces `List<Unit>`. - Ensure each `return@map` actually targets the lambda (label is the function name) and isn't a misintended non-local `return`. ## Reviewer summary Prefer small lambdas; push real logic into named functions or anonymous functions; replace imperative early-exits with `when`/`mapNotNull`/`filter` when it clarifies intent; and verify the produced type.
- When is an anonymous function preferable to a labeled return lambda?When early returns dominate the logic: an anonymous function's plain `return` reads more naturally than scattered return@map, and it still inlines like a lambda for the right APIs only if the function is inline; otherwise it's a normal call.
- How does mapNotNull help here?It lets you map to null to drop elements instead of returning a sentinel, and it returns a non-null list, removing branchy return@map logic.
A 30-line lambda with many return@map exits is like a paragraph with no sentences — break it into named, testable functions so each idea stands alone.
saying these in an interview costs you the question
- Defending huge lambdas as inherently fine for readability
- Not noticing the List<Unit> risk during refactors
- Never suggesting extraction or named functions
- Confusing return@map with a non-local return in review
- Ignoring declarative options like when/mapNotNull/filter