A teammate writes `val updated = user.let { it.copy(active = true) }` then later uses user expecting it to be active. What's wrong, and how does let's return semantics explain it?
answer
- let returns lambda result, not the receiver
- copy makes a new instance; original is unchanged
- Capture the result in a val/var to keep it
- let = transform; apply/also = return receiver
- Redundant let over a plain copy() call
basics
~10 slet returns the block's result, not the original object. The new active user is in updated; the original user is untouched. They should use updated, or pick a function that returns the object.
solid answer
~40 sThe bug is a misunderstanding of let's return value. `let` returns the **lambda result**, so `updated` holds the new copy with `active = true`, while `user` (especially since `copy` on a data class produces a *new* instance and doesn't mutate the original) is unchanged. The teammate then reads the stale `user`. Fixes: (1) use `updated` going forward; (2) if `user` is a `var`, reassign: `user = user.let { it.copy(active = true) }`, though here `let` adds nothing over a plain `user.copy(...)`; (3) if they actually wanted side-effecting configuration returning the same object, that's `apply`/`also`, but those don't help with immutable `copy`. The deeper point: `let` is for **transformation**, returning something new — not for mutating-in-place.
code
kotlin · 7 linesdata class User(val name: String, val active: Boolean)
val user = User("Ada", active = false)
val updated = user.let { it.copy(active = true) }
println(user) // User(name=Ada, active=false) -- unchanged
println(updated) // User(name=Ada, active=true) -- the let resultgo deeper
Recognizes that user is unchanged and the new value is in updated.
Explains both that let returns the lambda result and that copy is non-mutating, and gives the correct fix.
Notes let is redundant over a plain copy and explains why apply/also wouldn't help with immutable data.
Uses this to argue for immutable-data conventions and code-review rules against redundant scope-function wrapping.
## The core rule being tested `let` returns the **result of its lambda**: ```kotlin public inline fun <T, R> T.let(block: (T) -> R): R = block(this) ``` It does **not** return the receiver. So the value flows *out* through the `let` call's result. ## What the code actually does ```kotlin data class User(val name: String, val active: Boolean) val user = User("Ada", active = false) val updated = user.let { it.copy(active = true) } // updated == User("Ada", active = true) // user == User("Ada", active = false) <-- unchanged ``` Two things combine: 1. **`copy` is non-mutating** — on a `data class` it builds a brand-new instance; the original is immutable here (`val` fields). 2. **`let` returns the new instance**, assigned to `updated`. The original `user` reference is never touched. Using `user` afterwards reads the old, inactive value — the bug. ## Fixes - **Use the result:** thread `updated` everywhere the active user is needed (idiomatic for immutable data). - **Reassign a var:** `var user = ...; user = user.copy(active = true)`. Here `let` is redundant — `user.copy(...)` is clearer. - **Wrong tool for in-place config:** `apply`/`also` return the receiver, but they still can't mutate `val` fields; they'd only help with a mutable builder object. ## When `let` is the right call Use `let` when you genuinely want the **transformed result**: ```kotlin val dto = user.let { UserDto(it.name, it.active) } ``` Mental model: **value in as `it`, transformed value out**. If you want the *same object back*, you wanted `also` (it) or `apply` (this), not `let`.
- Would using apply instead of let fix it?No. apply returns the receiver and exposes it as this, but copy still produces a new object that apply would discard. You must capture the copy's result.
- Is let even needed here?No. user.copy(active = true) is clearer; the let wrapper adds an unnecessary block and the it indirection.
saying these in an interview costs you the question
- Believing let mutates the receiver in place
- Thinking copy modifies the original data class instance
- Suggesting apply/also as if they'd make copy mutate the original
- Not capturing the let result and then reading the stale variable