skip to content

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?

level: seniorimportance: nice to knowfreq 30%

answer

  1. Long return-heavy lambda = readability smell
  2. Extract to named fun + ::reference
  3. Anonymous fun = natural local return
  4. when/mapNotNull/filter over early exits
  5. Verify element type, beware List<Unit>

basics

~20 s

Long 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 s

Big 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
kotlin
// 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

for a junior

Recognizes the lambda is long but may only suggest comments.

for a middle

Suggests extracting a named function and verifying the result type.

for a senior

Weighs anonymous functions, mapNotNull/filter/when, and testability tradeoffs deliberately.

for a principal

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

context