skip to content

Closures & Captured Variables

Kotlin lambdas can capture and even mutate a var from the enclosing scope, unlike Java's effectively-final rule. Interviewers follow up on the cost: the compiler boxes that variable in a Ref object, which has real implications for loops and concurrency.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

What does it mean that a Kotlin lambda 'closes over' a variable, and what can a lambda do with a captured local variable that a Java lambda cannot?

level: juniorimportance: must knowfreq 70%

answer

  1. Closure = lambda + captured enclosing scope
  2. Kotlin can mutate captured var; Java cannot
  3. Captures vals, vars, params, outer this
  4. Same variable shared inside and outside
  5. State survives after enclosing fn returns

basics

~10 s

A Kotlin lambda can read and remember variables from the function around it. Unlike Java, it can also change a captured var, not just read it.

solid answer

~40 s

A closure is a lambda (or anonymous function) that captures and retains references to variables from its enclosing scope — function locals, parameters, or outer this. It can use them even after the enclosing function returns. The key difference from Java: Java only allows capturing values that are final or effectively final (read-only). Kotlin lets a lambda capture a var and reassign it; the change is visible both inside the lambda and to the surrounding code, because both share the same captured variable. This works for both inline and non-inline lambdas (with non-inline ones the compiler boxes the var into a Ref wrapper object). You capture vals too — the common, idiomatic case.

code

kotlin · 11 lines
kotlin
fun makeAccumulator(): (Int) -> Int {
    var total = 0           // captured mutable var
    return { delta ->
        total += delta      // mutating captured var — allowed in Kotlin
        total
    }
}

val acc = makeAccumulator()
println(acc(5))  // 5
println(acc(3))  // 8

go deeper

for a junior

Knows a lambda can use variables from the surrounding function and that Kotlin allows mutating a captured var.

for a middle

Explains that inside and outside share the same variable, and gives the Java effectively-final contrast precisely.

for a senior

Connects capture to higher-order functions/callbacks and notes the val vs var capture distinction and lifetime beyond the enclosing call.

for a principal

Frames closures as the foundation for callbacks/coroutines and discusses capture's implications for memory, identity, and design clarity.

## What a closure is A **closure** is a function value — a **lambda** (`{ ... }`) or **anonymous function** (`fun(...) { ... }`) — that **captures** variables from the scope where it is defined. "Captures" means it keeps a reference to those variables so it can read (and sometimes write) them, even if the lambda runs later, after the enclosing function has already returned. The set of variables a lambda can see and remember is its *captured environment*. Captured things include: - local `val`/`var` declared in the enclosing function - function parameters - the enclosing `this` (the receiver of the outer method) ## The Java difference In **Java**, a lambda or anonymous class may only capture variables that are `final` or *effectively final* — i.e. never reassigned after initialization. You cannot mutate a captured local from inside a Java lambda; it is a compile error. In **Kotlin**, a lambda can capture a `var` **and reassign it**, and the new value is visible both inside the lambda and outside it. Both sides refer to the *same* variable. ```kotlin fun countClicks(): () -> Int { var count = 0 // captured var return { count += 1 // mutate the captured var — legal in Kotlin, illegal in Java count } } val click = countClicks() println(click()) // 1 println(click()) // 2 — state persists in the closure ``` ## Capturing val (the common case) Most real code captures a read-only `val`: ```kotlin fun greeter(name: String): () -> Unit = { println("Hello, $name") } ``` Here `name` is captured by value-semantics (it never changes), and the returned lambda remembers it. ## Why this matters Closures are what make higher-order functions like `map`, `filter`, `forEach`, callbacks, and coroutines convenient — they let a small function body reach out and use surrounding context without you threading every value through parameters.

  • Does the captured var's change made inside the lambda affect the outer function's view of that variable?
    Yes. The lambda and the enclosing scope share the same variable, so a reassignment inside the lambda is visible outside it (and vice versa).
  • Can an anonymous function (fun(...) {}) capture variables the same way?
    Yes — anonymous functions are also closures and capture enclosing scope identically to lambdas.

A closure is like a backpack the lambda carries: it packs the variables it needs from home and can still use them anywhere it travels.

saying these in an interview costs you the question

  • Claiming Kotlin lambdas, like Java, can only capture final/read-only variables
  • Saying captured vars are copied so outer code never sees the change
  • Thinking only top-level/global variables can be captured
  • Confusing closure (captures scope) with a plain function reference

context

open as a page

When a Kotlin lambda captures a mutable var, what does the compiler actually emit on the JVM to make the mutation shared, and how does this differ from capturing a val?

level: middleimportance: should knowfreq 45%

basics

~10 s

For a captured var the compiler wraps it in a small holder object (a Ref) so the lambda and outer code point at the same box. A captured val needs no box.

open as a page

Explain what happens when you create lambdas inside a loop that capture the loop variable or a var updated in the loop. How does Kotlin's behavior compare to the classic capture pitfall, and how do you get a per-iteration snapshot?

level: seniorimportance: should knowfreq 40%

basics

~10 s

If lambdas capture one shared mutable var, they all see its final value. To freeze the current value per iteration, copy it into a new val inside the loop and capture that instead.

open as a page

What are the lifetime and memory implications of capturing variables in long-lived closures, and how can captured references cause leaks? Give Kotlin-specific guidance.

level: seniorimportance: nice to knowfreq 25%

basics

~10 s

A closure keeps its captured things alive as long as the closure lives. If a long-lived lambda captures a big or context object, that object can't be garbage-collected, causing a leak.

open as a page

How does capturing a mutable var interact with inline functions and concurrency? Discuss inline vs non-inline capture, and the thread-safety of mutating a captured var from multiple threads/coroutines.

level: principalimportance: nice to knowfreq 20%

basics

~20 s

Inline lambdas paste their body in place, so a captured var often needs no wrapper. But two threads mutating the same captured var race — the Ref field isn't synchronized; use atomics or proper concurrency instead.

open as a page