skip to content

Scope Functions

let, run, with, apply, and also all execute a block against an object; they differ in whether the object arrives as this or it and whether the block's result or the object comes back. Interviewers ask for the selection rule because misuse makes code noticeably worse.

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

explore

questions

25

What do Kotlin's apply { } and also { } return, and how does each give you access to the object inside the lambda?

level: juniorimportance: must knowfreq 80%

answer

  1. Both return the receiver, not the lambda result
  2. apply = this, also = it
  3. apply configures, also side-effects/logs
  4. let/run return last expression instead
  5. All are inline functions

basics

~10 s

Both apply and also return the same object you called them on. Inside apply you use it as this; inside also you use it as it. Neither changes what value comes out.

solid answer

~40 s

Both apply and also are extension functions that return the receiver object itself (the thing before the dot), not the result of the lambda. The difference is how the lambda accesses that object: apply passes it as the lambda receiver (this), so you can call members directly without a name; also passes it as the single argument it. Because they return the original object, you can keep chaining method calls afterward. Use apply for configuring an object (set properties, call setters) and also for side effects like logging, validation, or adding to a list, where the it name keeps the original object readable. Compare with let/run, which return the lambda's last expression instead.

code

kotlin · 7 lines
kotlin
val person = Person().apply {
    name = "Lena"   // this.name
    age = 30
}.also {
    println("created: ${it.name}") // it == person
}
// person is fully configured, logged, and === to the apply/also result

go deeper

for a junior

Knows both return the object and that apply=this, also=it.

for a middle

Explains why returning the receiver enables chaining and contrasts with let/run.

for a senior

Mentions the inline/lambda-with-receiver signatures and picks the idiomatic function per use case.

for a principal

Frames apply/also within the whole scope-function matrix (this/it x receiver-vs-lambda-result) and team readability conventions.

## The core fact `apply` and `also` are two of Kotlin's five **scope functions** (the others are `let`, `run`, and `with`). A *scope function* lets you run a block of code on an object inside a temporary scope. The thing you call the function on is the **receiver** — the object before the dot. The defining trait of `apply` and `also`: **they both return the receiver object itself**, not whatever the lambda evaluates to. This is what makes them ideal for fluent chains. ## How the object is exposed inside the lambda - **`apply`** uses a *lambda with receiver*. Inside the braces, the object is bound to **`this`**, so you can reference its members directly (often omitting `this`). Its signature is roughly `inline fun <T> T.apply(block: T.() -> Unit): T`. - **`also`** passes the object as a *regular argument* named **`it`**. Its signature is `inline fun <T> T.also(block: (T) -> Unit): T`. ```kotlin val list = mutableListOf(1, 2, 3) // apply: object is `this` val a = list.apply { add(4) // == this.add(4) add(5) } // returns the SAME list // also: object is `it` val b = list.also { println("size is ${it.size}") } // returns the SAME list println(a === b) // true — both are the original list ``` ## Why "returns the receiver" matters Because the original object comes back out, you can configure or peek at an object **in the middle of an expression** without breaking the chain: ```kotlin val user = User() .apply { name = "Ada" } // configure, get User back .also { log.info("built $it") } // log, get User back ``` Contrast: `let` and `run` return the **last expression** of the lambda, so they *transform* a value. `apply`/`also` never transform — they hand the object straight back. ## Quick mental model - `apply` → "configure this object" (`this`, returns object). - `also` → "do something on the side with `it`" (`it`, returns object). All four (`let`, `run`, `apply`, `also`) plus `with` are declared `inline`, so there is no lambda-allocation or call overhead at runtime.

  • If you wrote `val x = list.apply { add(1) }`, what is x?
    x is the same list object (with 1 appended) — apply returns the receiver, not the result of add.
  • Could you swap apply for also and keep `this.name = ...`?
    No. also exposes the object as it, not this, so you'd have to write it.name = ... instead.

apply is like handing a builder the object and saying 'set it up'; also is like passing it to an observer who notes something and hands it right back.

saying these in an interview costs you the question

  • Saying apply returns the lambda's last expression (that's let/run)
  • Claiming also uses this instead of it
  • Thinking apply/also create a copy rather than returning the same instance
  • Confusing apply with also's receiver style

context

open as a page

What does Kotlin's let scope function do, and how do you access the value inside its lambda?

level: juniorimportance: must knowfreq 80%

basics

~10 s

let runs a block of code on a value. Inside the block the value is called it, and let returns whatever the last line of the block produces.

open as a page

What do Kotlin's run { } and with(x) { } scope functions do, and what does each return?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Both run a block of code on an object you can call this on. run is called on the object; with takes the object as an argument. Both return the last expression in the block.

open as a page

What does the expression `name?.let { it.uppercase() } ?: "UNKNOWN"` do, and how does it handle a null `name`?

level: juniorimportance: must knowfreq 80%

basics

~10 s

If name is not null, it runs the block and uppercases it. If name is null, the block is skipped and the whole expression becomes "UNKNOWN". The Elvis operator (?:) supplies the fallback.

open as a page

What are the two axes that distinguish Kotlin's five scope functions (let, run, with, apply, also), and how do they map to choosing one?

level: juniorimportance: must knowfreq 75%

basics

~20 s

Two things differ: how you refer to the object inside the block (as 'this' or as 'it'), and what the block gives back (the block's last line, or the original object). Pick based on those two needs.

open as a page

Show how apply is used for builder-style object configuration, and explain why it reads better than assigning the object to a variable first.

level: middleimportance: must knowfreq 70%

basics

~20 s

apply lets you create an object and set its properties in one block, using this so you skip repeating the variable name. It returns the finished object, so the whole thing is a single expression.

open as a page

Explain the x?.let { } idiom. What exactly happens when x is null versus non-null?

level: middleimportance: must knowfreq 85%

basics

~10 s

x?.let { } runs the block only when x is not null. If x is null, the block is skipped and the whole expression is null. Inside the block x is non-null.

open as a page

Both apply and also return the original object. When should you use apply versus also, and why?

level: middleimportance: must knowfreq 68%

basics

~20 s

Use apply to set up an object's own properties (the object is 'this'). Use also for side actions like logging or checks that take the object as 'it'. Both hand the object back unchanged in identity.

open as a page

How is also used for side-effecting and logging inside a call chain, and why is it a better fit than apply for that job?

level: middleimportance: should knowfreq 60%

basics

~20 s

also lets you slip a side effect — like logging, validating, or saving — into a chain without breaking it. It hands the object back unchanged, and you read it as it, which keeps logs and effects clear.

open as a page

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?

level: middleimportance: should knowfreq 60%

basics

~10 s

let 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.

open as a page

How does x?.run { } behave with a nullable receiver, and why can't with(x) { } offer the same null guard?

level: middleimportance: should knowfreq 55%

basics

~20 s

x?.run { } runs the block only when x is not null, otherwise the whole thing is null. with(x) always runs because x is just a parameter, so a null x would make member calls inside crash or need an explicit check.

open as a page

Why would you choose run or with over apply for configuring an object, and what is the trap if you pick the wrong one?

level: middleimportance: should knowfreq 60%

basics

~20 s

Use run or with when you need the computed result of the block. Use apply when you need the object back. The trap: run/with give you the block's last value, so if you expected the object you'll get the wrong thing.

open as a page

You have `user?.apply { ... }` and `user?.also { ... }`. How do these differ from `let`/`run` for nullable handling, and why are they usually NOT the right choice for transform-or-default?

level: middleimportance: should knowfreq 45%

basics

~20 s

apply and also run their block only when the value is non-null, but they return the object itself, not the block's result. So they are for side effects (logging, configuring), not for producing a transformed value or a default.

open as a page

When guarding a nullable value, what is the difference between using `value?.let { ... }` and `value?.run { ... }`, and when would you pick each?

level: middleimportance: should knowfreq 60%

basics

~20 s

Both run their block only when value is non-null. let gives you the value as it; run gives it as this, so you call its members directly without a name. Both return the block's result.

open as a page

let and run both return the lambda result. How do you choose between them, and how does the this-vs-it distinction affect chaining and null safety?

level: middleimportance: should knowfreq 60%

basics

~20 s

Both give back the block's last line. Pick let when you want the object as a named 'it' (good for passing it around or null-safe checks). Pick run when you want it as 'this' so you can call its members directly.

open as a page

Given a chain mixing apply and also with a non-Unit last line in the block, what value flows out? Trace it and explain.

level: seniorimportance: should knowfreq 45%

basics

~10 s

No 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.

open as a page

How does let behave when chained on collections or in transformation pipelines, and how is it different from map?

level: seniorimportance: should knowfreq 45%

basics

~20 s

let treats the whole value as one thing: it passes the entire object in as it and returns one result. map transforms each element of a collection. So list.let acts on the list itself; list.map acts on items.

open as a page

Explain the signatures of run and with, including how the lambda's receiver type works and what inline/contracts imply for control flow and capture.

level: seniorimportance: should knowfreq 35%

basics

~20 s

run is an extension that takes a lambda where the object is the receiver; with is a top-level function taking the object plus that lambda. Both are inline, so the block runs in place, allowing non-local returns and avoiding extra objects.

open as a page

Why does `if (user.address != null) { user.address.city }` sometimes fail to smart-cast a nullable property, and how does `user.address?.let { it.city }` solve it?

level: seniorimportance: should knowfreq 55%

basics

~20 s

Smart-cast fails on mutable (var) or non-local properties because their value could change between the null check and use. Capturing the value once with ?.let { it ... } binds it to a stable local, so the compiler knows it stays non-null inside the block.

open as a page

What problems arise from nesting receiver-based scope functions (apply/run/with), and how do this/it choices and labels help you avoid them?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Nesting blocks that all use 'this' makes it unclear which object a bare property refers to, and inner this hides outer this. Prefer 'it'-based functions when nesting, name the parameter, or qualify this with a label.

open as a page

You see new code using also { } to initialize fields and apply { } to register the object in a global registry. Critique these choices and propose the idiomatic split.

level: seniorimportance: nice to knowfreq 30%

basics

~10 s

The two are swapped. apply (this) is for configuring the object's own fields; also (it) is for side effects like registering it somewhere. Flip them so the code reads as intended.

open as a page

When does overusing let hurt code quality, and what are its readability and nesting pitfalls (especially nested ?.let chains)?

level: seniorimportance: nice to knowfreq 35%

basics

~20 s

let is overused when it wraps simple calls or stacks many nested blocks. That makes code hard to read, with ambiguous its and deep nesting. Plain ifs, named variables, or early returns are often clearer.

open as a page

When reviewing code, how do you decide between with(x) { } and x.run { } (and when to use the receiver-less run { }), and what readability pitfalls do you watch for?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Use with(x) for a standalone 'do several things with this object' statement; use x.run when chaining or when x may be null (x?.run). Use plain run { } to scope locals. Watch for nested this shadowing and overlong blocks.

open as a page

Compare deeply nested `a?.let { b?.let { c?.let { ... } } }` guarding against a flattened `?.` chain with a single Elvis. What are the readability, short-circuit, and error-reporting tradeoffs?

level: seniorimportance: nice to knowfreq 35%

basics

~20 s

A flat a?.b?.c ?: default is shorter and reads top-to-bottom, but you can't tell which step was null. Nested lets let you handle each step separately or do work between steps, at the cost of deep indentation.

open as a page

Scope functions can hurt readability when overused. What anti-patterns do you watch for, and what conventions keep chains clear?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Avoid long stacked chains, using scope functions just to save a variable, mixing this and it confusingly, and using them only for null checks where an if or early return is clearer. Pick the one whose this/it/return matches your intent.

open as a page