skip to content

this-vs-it & Choosing One

Choosing a scope function is a two-axis decision: does the block want this or it, and should the expression evaluate to the object or the block's result. Being able to state that rule cleanly is the point of the interview question.

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

questions

5

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%

answer

  1. Two axes: this vs it, and result vs original
  2. apply=this+self, also=it+self
  3. let=it+result, run/with=this+result
  4. self-returners are for configuring/chaining
  5. with is not an extension (no ?. chaining)

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.

solid answer

~40 s

Kotlin's scope functions vary on two axes. Axis 1 — context object reference: run/with/apply expose it as the receiver this (you can drop the name and call members directly); let/also expose it as the lambda argument it (an explicit, renamable parameter). Axis 2 — return value: let/run/with return the lambda result (the last expression); apply/also return the original object (for chaining/configuring). So: apply = this + original object (configure-and-return); also = it + original object (side effects); run/with = this + result (compute on a receiver); let = it + result (transform/null-safe map). Choose by asking: do I want a clean receiver (this) or an explicit name (it), and do I want a computed result or the same object back?

code

kotlin · 6 lines
kotlin
val numbers = mutableListOf(1, 2, 3)
val result = numbers
    .also { println("before: $it") }   // it + original -> still the list
    .apply { add(4) }                  // this + original -> still the list
    .let { it.sum() }                  // it + result -> Int
println(result) // 10

go deeper

for a junior

Should name the two axes and correctly place at least apply and let on them.

for a middle

Maps all five precisely and picks one for a given snippet with justification.

for a senior

Explains the receiver-lambda type (T.() -> R) vs (T) -> R and the with-is-not-an-extension nuance.

for a principal

Frames the choice as a readability/intent convention and can codify team guidelines (e.g. apply only for config, also only for side effects).

## What scope functions are Kotlin's standard library provides five **scope functions** — `let`, `run`, `with`, `apply`, and `also` — declared in `Standard.kt`. They all do one thing: execute a block (lambda) **in the context of an object**, giving you a temporary scope to operate on that object. They differ only in *how the object is exposed* and *what is returned*. Choosing the right one is purely a readability decision; functionally you can often achieve the same effect with any of them. ## Axis 1 — Context object: `this` (receiver) vs `it` (argument) - **Receiver (`this`)** — used by `run`, `with`, `apply`. The lambda is a *function literal with receiver* (`T.() -> R`). Inside, the object **is** `this`, so you can call its members without any qualifier: `name` instead of `it.name`. You may still write `this` explicitly. Best when you make many member calls. - **Argument (`it`)** — used by `let`, `also`. The lambda is an ordinary `(T) -> R`. The object is the single parameter, named `it` by default, but you can rename it: `user.let { u -> ... }`. Best when you pass the object to other functions (`it`), when you want to rename it for clarity, or when nesting scopes (to avoid `this` shadowing). ## Axis 2 — Return value: lambda result vs original object - **Lambda result** — `let`, `run`, `with` return whatever the block's **last expression** evaluates to. Use them to **transform** or **compute** a value. - **Original object** — `apply`, `also` return the **same object** they were called on, ignoring the block's result. Use them to **configure** an object or run **side effects** while keeping the object in a chain. ## The full mapping | Function | Object as | Returns | Typical use | |----------|-----------|---------|-------------| | `let` | `it` | lambda result | transform value; null-safe `?.let` | | `run` | `this` | lambda result | compute on a receiver; run a block | | `with` | `this` | lambda result | group calls on a non-null object (not an extension) | | `apply` | `this` | original object | configure/build then return it | | `also` | `it` | original object | side effects (log, validate) in a chain | ```kotlin data class User(var name: String = "", var age: Int = 0) val user = User().apply { // this + original -> configure & return User name = "Ada" age = 30 }.also { // it + original -> side effect, still User println("created $it") } val label: String = user.let { // it + result -> transform to String "${it.name} (${it.age})" } val summary = with(user) { // this + result -> compute using members "$name is $age" } ``` ## How to choose, mechanically 1. Do I want the **same object back** (chaining/config)? → `apply` or `also`. Otherwise a **computed result** → `let`, `run`, or `with`. 2. Do I prefer a **clean receiver** (`this`, many member calls) or an **explicit name** (`it`, passing it around / nesting)? → `apply`/`run`/`with` for `this`; `let`/`also` for `it`. Note `with(x){}` is a regular function taking the object as an argument (so it doesn't help with null-safe `?.`), whereas `run` is also available as an *extension* `x.run{}` that does chain with `?.`.

  • If apply and run both use this, what is the single difference?
    Return value: apply returns the original object; run returns the lambda's last expression.
  • Why might you prefer let over run even though run is shorter?
    let exposes the object as a renamable it, which is clearer when nesting scopes or passing the object to another function as an argument.

Think of a workshop bench: 'this' hands you the object already mounted on the bench (call its tools directly); 'it' hands you the object in a labeled box you can pass around.

saying these in an interview costs you the question

  • Claiming all five are interchangeable with no semantic difference
  • Saying apply returns the lambda result
  • Thinking it cannot be renamed
  • Believing this and it are the same thing under the hood
  • Not knowing with returns the lambda result, not the object

context

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

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

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

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