Explain Unit's role in lambdas and SAM/functional-type conversions: the last-expression coercion to Unit and why () -> Unit lambdas don't need an explicit return.
answer
- Lambda result = last expression
- Expected Unit => last expression coerced to Unit (value ignored)
- Why forEach { list.add(it) } compiles (add returns Boolean)
- Same coercion for void SAMs (Runnable)
- Only when expected type returns Unit, only last expr
basics
~20 sWhen a lambda's expected type returns Unit, Kotlin ignores whatever the last expression produces and treats the result as Unit. So you never need to add return Unit or worry that the last line returns something else.
solid answer
~50 sA lambda's result is normally its last expression. But Kotlin applies **Unit coercion**: if the expected function type is `() -> Unit`, the compiler discards the type of the last expression and supplies `Unit` instead. This is why `forEach { list.add(it) }` compiles even though `add` returns `Boolean` -- the `(T) -> Unit` parameter coerces the Boolean away. The same applies to SAM conversions where the abstract method returns void (e.g. `Runnable`): the lambda body's last value is ignored. Without this rule you'd have to append a bare `Unit` or refactor every side-effecting lambda. The coercion only applies at the **last statement** of a Unit-typed lambda; it does not silently swallow values in a lambda whose expected type returns a real type. Implementation-wise the generated `invoke`/SAM method returns `Unit.INSTANCE` (or void for the SAM's void method).
code
kotlin · 10 linesval sink = mutableListOf<Int>()
// add() returns Boolean, but forEach wants (Int) -> Unit
listOf(1, 2, 3).forEach { sink.add(it) } // OK: Boolean coerced to Unit
// No coercion when a real return type is expected:
val bad: (Int) -> Int = { it.toString() } // compile error: String is not Int
// SAM with void method ignores the lambda's value:
val task = Runnable { sink.size } // Int discarded; run() is voidgo deeper
Knows side-effecting lambdas 'just work' without writing return Unit.
Can state that a lambda returns its last expression and that Unit-typed lambdas ignore that value.
Names coercion to Unit precisely, its scope (last expr, expected Unit only), and applies it to forEach and void SAMs.
Explains the emitted invoke/SAM return, the coercion's boundaries vs type errors, and the ergonomics rationale for the rule.
## Lambdas return their last expression A Kotlin lambda implicitly returns the value of its **last expression**: ```kotlin val len: (String) -> Int = { it.length } // last expr Int -> result Int ``` ## Unit coercion (the key rule) When the **expected** function type returns `Unit`, the compiler **coerces the last expression to Unit**, discarding its actual type. This is called *coercion to Unit*: ```kotlin val items = mutableListOf<Int>() listOf(1, 2, 3).forEach { items.add(it) } // add returns Boolean, but param is (Int) -> Unit ``` `forEach` takes `(T) -> Unit`. `MutableList.add` returns `Boolean`. Without coercion the lambda's inferred result would be `Boolean`, mismatching `Unit`. The rule makes the last expression's value **ignored** and the lambda's result `Unit`, so it compiles cleanly. You do **not** write `return Unit`. ## SAM / functional interfaces with void The same applies to **SAM conversions** where the single abstract method returns `void`: ```kotlin val r = Runnable { computeAndReturnInt() } // run(): void; the Int is discarded ``` The lambda satisfies `Runnable` even though its body yields an `Int`, because `run()` is effectively Unit/void-returning. ## Scope and limits - Coercion applies **only** when the expected type returns Unit. In `val f: (Int) -> Int = { it.toString() }` there is **no** coercion -- that is a type error because `String` is not `Int`. - It applies to the **last** expression; it doesn't change earlier statements. - A non-local `return` inside the lambda still obeys the enclosing function. ## What the compiler emits For a `() -> Unit` lambda the generated `Function0.invoke()` returns `kotlin.Unit.INSTANCE`; for a SAM with a void method, the generated method returns `void`. Either way you never hand-write the return. ## Keywords/APIs Coercion to Unit, `() -> Unit`, SAM conversion, `Runnable`/`Consumer`, `forEach`, implicit last-expression return, `Unit.INSTANCE`.
- Does Unit coercion fire for a lambda typed `(Int) -> Boolean`?No. Coercion only applies when the expected result type is Unit; for Boolean the last expression must actually be Boolean.
- Why does `forEach { mutableList.add(it) }` not raise a type mismatch on add's Boolean result?forEach's parameter is (T) -> Unit, so the Boolean from add is coerced to Unit and discarded.
saying these in an interview costs you the question
- Claiming you must write `return Unit` or a bare `Unit` in side-effecting lambdas
- Saying Unit coercion silently discards values in any lambda regardless of expected type
- Not knowing forEach takes (T) -> Unit
- Thinking the coercion applies to every statement, not just the last
- Believing a (Int) -> Int lambda can end in a String via coercion