skip to content

Lambdas & Anonymous Functions

The literal forms for writing a function inline: lambdas with their implicit it, closures over surrounding state, multi-statement bodies, and the anonymous-function alternative. Interviewers care because the return semantics differ between the two forms in ways that bite.

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

explore

questions

25

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

What is the basic brace syntax of a Kotlin lambda, and what does the implicit `it` refer to?

level: juniorimportance: must knowfreq 85%

basics

~20 s

A lambda is code in curly braces: { x -> body }. When it takes exactly one parameter you can skip naming it and use the built-in name it instead, like list.map { it * 2 }.

open as a page

In a multi-line Kotlin lambda, how is the value the lambda produces determined? Show how this works with a map call.

level: juniorimportance: must knowfreq 78%

basics

~10 s

The last line in the lambda's body is its result automatically. You don't write a return keyword. Whatever that final line evaluates to is what the lambda hands back.

open as a page

What is the trailing-lambda convention in Kotlin, and how do you write `list.filter { it > 0 }` using it?

level: juniorimportance: must knowfreq 80%

basics

~20 s

If the last argument to a function is a lambda, you can write it outside the parentheses. So instead of filter({ it > 0 }) you write filter { it > 0 }, which reads more cleanly.

open as a page

How does a bare 'return' behave inside an anonymous function versus inside a lambda passed to forEach? Explain local vs non-local return.

level: middleimportance: must knowfreq 60%

basics

~20 s

In an anonymous function, 'return' exits just that function. In a lambda, a bare 'return' exits the whole enclosing function (a non-local return). That different behavior is the main reason to pick an anonymous function.

open as a page

What does `return@map` do, and how does it differ from a bare `return` inside a lambda passed to map?

level: middleimportance: must knowfreq 70%

basics

~10 s

return@map exits just the current lambda call and supplies that element's result, like 'continue' for the iteration. A bare return would try to exit the whole surrounding function instead.

open as a page

When can you drop the empty `()` entirely, as in `repeat(3) { }` vs `run { }`? Explain the rule precisely.

level: middleimportance: must knowfreq 65%

basics

~20 s

You drop the parentheses only when the lambda is the function's single argument and nothing else is left inside them. run takes only a lambda, so run { }. repeat(3) { } still needs (3) because 3 is a separate argument.

open as a page

What is an anonymous function in Kotlin, and how does its syntax differ from a lambda expression?

level: juniorimportance: should knowfreq 45%

basics

~20 s

An anonymous function is a function without a name, written with the fun keyword like fun(x: Int): Int { return x * 2 }. Unlike a lambda, you write parameter and return types explicitly and use a normal return.

open as a page

When does an anonymous function's block body return Unit unexpectedly, and how do explicit vs inferred return types behave for block versus expression bodies?

level: middleimportance: should knowfreq 35%

basics

~20 s

An anonymous function with a block body { } returns Unit unless you declare a return type or use a return statement. An expression body (= ...) infers its type from the expression. So a block body without an explicit type can surprise you by returning Unit.

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

How do you destructure a parameter inside a lambda, and what are the rules and limits of destructuring lambda parameters?

level: middleimportance: should knowfreq 60%

basics

~20 s

If a lambda parameter is something like a Pair or a data class, you can split it into its parts inside parentheses, e.g. map.forEach { (key, value) -> ... }, instead of using one name and calling .first/.second.

open as a page

What does the underscore `_` mean in a lambda parameter list, and when would you use it?

level: middleimportance: should knowfreq 50%

basics

~20 s

Writing _ as a lambda parameter name means 'I won't use this one.' It's a placeholder so you don't have to invent a name for a value you ignore, like { _, value -> value }.

open as a page

Where does the label in `return@let` or `return@forEach` come from, and when must you declare an explicit label instead?

level: middleimportance: should knowfreq 52%

basics

~20 s

The label after @ is automatically the name of the function the lambda was given to, like let or forEach. You write your own label when the auto name is ambiguous or you have nested lambdas of the same function.

open as a page

A function takes two function-type parameters. How do you call it, and what is the idiomatic way to pass both lambdas cleanly?

level: middleimportance: should knowfreq 45%

basics

~10 s

Only the last lambda can sit outside the parentheses. The earlier lambda stays inside, usually passed by name with the named-argument syntax so the call reads clearly.

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 goes wrong when you nest lambdas that both rely on the implicit `it`, and how should you handle it?

level: seniorimportance: should knowfreq 45%

basics

~10 s

If a lambda inside another lambda both use it, the inner it hides the outer one, so you can't reach the outer value and the code is confusing. Fix it by naming the parameters.

open as a page

How does Kotlin infer the type of `it` (or named lambda parameters), and how can you make the parameter type explicit when needed?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Kotlin figures out the type of it from the function you pass the lambda to (its expected type). If that's unclear, you can write the type yourself, like { it: Int -> it * 2 }.

open as a page

How does the expected return type of a higher-order function affect what the last expression of a multi-statement lambda must be? Contrast forEach (Unit) with map (R).

level: seniorimportance: should knowfreq 44%

basics

~20 s

If the function expects a Unit-returning lambda (like forEach), Kotlin ignores whatever the last line produces. If it expects a real value (like map), the last expression must produce that value and its type drives the result type.

open as a page

As an API designer, why does the position of a function-type parameter matter, and how do you order parameters to give callers clean trailing-lambda call sites?

level: seniorimportance: should knowfreq 25%

basics

~10 s

Only the last parameter can be a trailing lambda, so you put the main callback last. That way callers write a clean block after the parentheses instead of a noisy nested lambda.

open as a page

How does the trailing-lambda syntax interact with overload resolution and SAM conversion? When can it cause an ambiguity or surprising call?

level: seniorimportance: should knowfreq 30%

basics

~20 s

The trailing lambda is just normal sugar, so the compiler still matches it to a function-type parameter. Problems appear when two overloads both accept a final function-type argument — then the call can be ambiguous.

open as a page

How do you declare a receiver type on an anonymous function, and how does an anonymous function help with overload resolution where a lambda would be ambiguous?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

You can write fun Foo.(x: Int): Int { ... } to give an anonymous function a receiver, producing a function-with-receiver type. Because anonymous functions state parameter and return types explicitly, they can resolve overloads that a bare lambda leaves ambiguous.

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

A teammate writes a 30-line map lambda with several return@map early-exits and intermediate vals. As a reviewer, what concerns and alternatives would you raise about multi-statement-body returns?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Long lambdas with many early exits are hard to read and test. Suggest extracting the body into a named function, or using an anonymous function with plain returns, so the transformation logic is clear and reusable.

open as a page

As a tech lead, what guidelines would you give for choosing an anonymous function over a lambda, and what are the maintainability and performance implications?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

Prefer concise lambdas by default. Use an anonymous function when you need a plain local return without a label, an explicit return type, a stated receiver, or to resolve overload ambiguity. Both compile to the same function types, so there is no inherent performance gap.

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