Given a chain mixing apply and also with a non-Unit last line in the block, what value flows out? Trace it and explain.
answer
- apply/also discard block result, return receiver
- Block type is (T)->Unit / T.()->Unit, last expr ignored
- let/run return the last expression instead
- Pin value back to receiver at each boundary
- Non-Unit last line compiles silently
basics
~10 sNo matter what the last line of an apply or also block evaluates to, the chain keeps flowing the original object forward. The block's result is discarded; only the receiver comes out.
solid answer
~40 sBoth apply and also discard the lambda's result and return the receiver. So even if the last statement in the block is a non-Unit expression — say add() returning a Boolean, or a property read — that value is thrown away and the original object continues down the chain. This is the key contrast with let/run, which return the lambda's last expression. A common trap: a candidate expects the Boolean from list.apply { add(x) } or treats also like let. Tracing rule: at each apply/also boundary, mentally pin the value back to the receiver. Because the block type is (T) -> Unit (also) or T.() -> Unit (apply), the compiler doesn't even require a Unit-typed last line; it just ignores whatever it produces.
code
kotlin · 5 linesval list = mutableListOf(1)
.apply { add(2) } // Boolean ignored -> list
.also { it.add(3) } // Boolean ignored -> list
.apply { sum() } // Int ignored -> list
println(list) // [1, 2, 3] (a List, never Boolean/Int)go deeper
Recognizes apply/also return the object but may be unsure why a non-Unit last line compiles.
Correctly traces that the receiver flows out and contrasts with let/run.
Explains the (T)->Unit block signature and the implicit-discard rule, and predicts the type after swapping to let.
Articulates the design rationale (pass-through vs transform) and the readability/foot-gun trade-off of silent discards in chains.
## The rule, precisely The block parameter types are: - `apply`: `block: T.() -> Unit` - `also`: `block: (T) -> Unit` The declared return of the **block** is `Unit`, but Kotlin allows the last expression of a `Unit`-returning lambda to be any type — it's simply **coerced/ignored**. The **function** `apply`/`also` then returns `T` (the receiver). So: > At every `apply` or `also`, the value flowing out is the receiver, regardless of the block's last expression. ## Worked trace ```kotlin val out = mutableListOf("a") .apply { add("b") } // add returns Boolean(true) -> IGNORED; out value = the list [a, b] .also { it.add("c") } // add returns Boolean -> IGNORED; value = same list [a, b, c] .apply { size } // size is Int -> IGNORED; value = same list // out : MutableList<String> = [a, b, c] ``` Every boundary pins the value back to the list. `out` is the `MutableList`, never a `Boolean` or `Int`. ## Contrast with let / run (so the difference is sharp) ```kotlin val n = mutableListOf("a") .let { it.add("b") } // let RETURNS the lambda result -> Boolean true // n : Boolean = true (the list is gone from the chain) val m = mutableListOf("a") .run { add("b"); size } // run returns last expression -> Int // m : Int ``` `let`/`run` *transform*; `apply`/`also` *pass through*. ## Why the language allows a non-Unit last line Kotlin permits any last expression in a lambda whose expected return type is `Unit` — it's an implicit "discard." That's why `apply { add(x) }` compiles even though `add` returns `Boolean`. There's no warning by default, which is exactly why this trips people up. ## Practical guidance - If you *want* the last expression's value, you chose the wrong function — use `let` or `run`. - If you want the object back no matter what, `apply`/`also` are correct and you can safely end the block on any expression. - Beware accidentally relying on a value from inside an apply/also block — it never escapes.
- Change the first .apply to .let — what is the chain's type after it?Boolean. let returns the lambda result (add returns Boolean), so the list no longer flows; subsequent .also would be on a Boolean.
- Does the compiler warn that add()'s Boolean is discarded inside apply?No. The block's expected return is Unit, so the last expression is implicitly discarded with no default warning — a common source of confusion.
saying these in an interview costs you the question
- Believing list.apply { add(x) } returns the Boolean from add
- Treating also/apply as transforming functions like let/run
- Expecting a value computed inside apply/also to escape the block
- Not knowing the block's last expression is ignored