When you partially apply a function by capturing a mutable variable in a closure, what surprising behavior can occur, and how do you make the captured value stable?
answer
- Closures capture the variable, not a snapshot
- Captured var shares a live Ref cell — sees later writes
- while + var i loop => all lambdas see final i
- Fix via function param or copy into a local val
- for/map bind a fresh val per element — safe to close over
basics
~20 sKotlin closures capture variables by reference, not by snapshot. If you capture a var and later change it, the partially applied function sees the new value. Capture a val (or copy the value into one) to lock it in.
solid answer
~50 sKotlin closures capture *variables*, not just their current values — and for a `var` the closure shares the live mutable cell (implemented via a `Ref` wrapper on the JVM). So a closure built for partial application that captures a `var` will observe later mutations, and a closure built inside a loop over the loop variable can all see the final value. To make a partially-applied argument stable, capture a `val`: either fix the value through a function parameter (`fun fix(x: Int): (Int) -> Int = { y -> x + y }` — `x` is effectively immutable per call) or copy into a local `val` before building the lambda (`val snapshot = current; { -> use(snapshot) }`). With `for ((i) ...)` and lambdas, prefer mapping/closing over the immutable loop element. The rule: capture immutables for predictable partial application; reach for a mutable capture only when you intentionally want shared, evolving state.
code
kotlin · 9 lines// Pitfall
var i = 0
val fns = mutableListOf<() -> Int>()
while (i < 3) { fns += { i }; i++ }
println(fns.map { it() }) // [3, 3, 3]
// Stable: fresh immutable per element
val safe = (0 until 3).map { n -> { n } }
println(safe.map { it() }) // [0, 1, 2]go deeper
Recognizes that changing a captured var afterward changes what the lambda sees, and knows to capture a val instead.
Explains the loop capture pitfall and fixes it by closing over a fresh immutable binding.
Describes the JVM Ref-boxing mechanism and prescribes function-parameter or local-val capture to stabilize partially applied arguments.
Adds concurrency reasoning (shared captured var needs atomics/synchronization) and sets guidance to default to immutable capture in partial-application helpers.
## The trap: closures capture variables, not snapshots When a lambda captures a variable, Kotlin captures the **variable itself**, not a frozen copy of its value at capture time. For a `val` this is invisible (the value never changes). For a **`var`**, the closure shares the same mutable storage cell — on the JVM, Kotlin boxes the `var` into a `Ref.IntRef`/`ObjectRef` so both the outer code and the lambda read/write the same cell. ```kotlin var level = "INFO" val log: (String) -> Unit = { msg -> println("[$level] $msg") } log("starting") // [INFO] starting level = "WARN" log("careful") // [WARN] careful <-- the partial captured the variable, not "INFO" ``` If you *intended* to partially apply `log` with the level fixed to `INFO`, this is a bug: the fixed argument silently changed. ## The classic loop pitfall ```kotlin val makers = mutableListOf<() -> Int>() var i = 0 while (i < 3) { makers += { i } // all close over the SAME var i i++ } println(makers.map { it() }) // [3, 3, 3] — not [0, 1, 2] ``` Every lambda shares the one `i`, which ends at 3. ## How to make the captured value stable **1. Capture through a function parameter (a fresh immutable per call):** ```kotlin fun fixLevel(level: String): (String) -> Unit = { msg -> println("[$level] $msg") } val info = fixLevel("INFO") // INFO is locked in for this closure ``` A function parameter is effectively a fresh `val` for each invocation, so each returned closure captures its own stable value. **2. Copy into a local `val` before building the lambda:** ```kotlin var level = "INFO" val snapshot = level // immutable copy val log = { msg: String -> println("[$snapshot] $msg") } level = "WARN" // does not affect log ``` **3. Use a `val` loop element (Kotlin's `for` already binds a fresh val):** ```kotlin val makers = (0 until 3).map { n -> { n } } // n is a fresh val per element println(makers.map { it() }) // [0, 1, 2] ``` Kotlin's `for (x in xs)` and the `n` parameter of `map`'s lambda are each a **fresh immutable binding per iteration**, so closing over them is safe — unlike the manual `var i` while-loop above. ## The principle - Capture an **immutable** (`val`, a function parameter, or a fresh loop element) when partially applying, so the fixed argument can't change underneath you. - Capture a **mutable** (`var`) only deliberately, when you want the closure to track evolving shared state. This is also relevant under concurrency: a shared captured `var` mutated from multiple threads needs synchronization or atomics.
- Why does for (x in xs) avoid the while-loop capture bug?Each iteration of for binds a fresh, effectively immutable x; closures capture distinct bindings, so each sees its own value. The manual while loop reuses one mutable var i shared by all closures.
- How is a captured var represented on the JVM, and why does that explain the shared-mutation behavior?Kotlin boxes it into a Ref wrapper (e.g., IntRef/ObjectRef) so the outer scope and the lambda hold the same reference cell; writes through either are visible to both.
Capturing a var is like writing 'see whiteboard' on a sticky note — when someone erases the board, your note now points to whatever's there. Capturing a val is photographing the board.
saying these in an interview costs you the question
- Believing a closure freezes the value at capture time
- Not recognizing the while+var loop capture bug
- Assuming capturing a var is always safe for partial application
- Ignoring thread-safety of a shared captured var under concurrency