How are the scope functions `also` and `apply` declared as generic-receiver extensions, and how do their receiver/return shapes differ?
answer
- also: `it`, returns receiver
- apply: `this`, returns receiver
- let/run return the lambda result instead
- Both inline + callsInPlace EXACTLY_ONCE
- Generic receiver T, not reified
basics
~20 sBoth work on any type because they take a generic receiver T. also passes the object as it and returns it; apply makes the object the lambda's this and also returns it. Both return the original object.
solid answer
~40 s`also` and `apply` are generic-receiver extensions of the form `fun <T> T.also(block) : T`. `also`'s lambda is `(T) -> Unit`, so the object arrives as the parameter `it` and the function returns the same `T` (the receiver). `apply`'s lambda is `T.() -> Unit` — a **function with receiver** — so inside, `this` is the object, and it likewise returns `T`. Both return the **original** object (unlike `let`/`run`, which return the lambda result). Both are declared `inline`, so the lambda is inlined with no allocation, and they are annotated with `@OptIn`/contracts (`callsInPlace(block, EXACTLY_ONCE)`) so the compiler knows the block runs exactly once, enabling definite-assignment of `val`s set inside. The receiver is **non-null** `T` in the stdlib versions, so calling on a nullable value needs `?.also { }`.
code
kotlin · 6 linesdata class Server(var host: String = "", var port: Int = 0)
fun build(): Server =
Server()
.apply { host = "localhost"; port = 8080 } // this == Server, returns it
.also { println("built $it") } // it == Server, returns itgo deeper
Knows apply uses this and also uses it, and both return the object.
Distinguishes lambda-with-receiver vs ordinary lambda and contrasts with let/run return shapes.
Explains inline + EXACTLY_ONCE contracts and chooses the right scope function for configure vs side-effect.
Discusses readability/shadowing tradeoffs across the scope-function family and when reified would (or wouldn't) be needed in similar generic-receiver utilities.
## Declarations (simplified stdlib) ```kotlin public inline fun <T> T.also(block: (T) -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block(this) return this } public inline fun <T> T.apply(block: T.() -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block() return this } ``` Both use a **generic receiver** `T`, so they apply to *any* type. ## The key axes | Function | How object enters lambda | Lambda type | Returns | |----------|--------------------------|-------------|---------| | `also` | as parameter `it` | `(T) -> Unit` | the receiver `T` | | `apply` | as receiver `this` | `T.() -> Unit` | the receiver `T` | | `let` | as parameter `it` | `(T) -> R` | lambda result `R` | | `run` | as receiver `this` | `T.() -> R` | lambda result `R` | - **Lambda with receiver** (`T.() -> Unit`): inside, `this` is the object; you call members directly. Used by `apply`/`run`. - **Ordinary lambda** (`(T) -> Unit`): the object is the parameter `it`. Used by `also`/`let`. ## When to use which - `apply` — **configure** an object then return it: `Person().apply { name = "A"; age = 1 }`. - `also` — **side effect** (logging, validation) without shadowing `this`: `user.also { log(it) }`. ## inline + contracts Both are `inline`, so no lambda object is allocated and non-local `return` works. The `contract { callsInPlace(block, EXACTLY_ONCE) }` tells the compiler the block runs exactly once, which lets you assign a `val` inside the block and have it count as definitely assigned afterward. ## Nullability The stdlib receiver is non-null `T`. On a nullable value, chain with a safe call: ```kotlin val u: User? = find() u?.apply { activate() } // runs only if non-null, returns User? ``` ## Generic, not reified `T` here is an ordinary type parameter — **not** `reified`. Reified is only needed when you must access the runtime `Class`/type inside the body (e.g. `inline fun <reified T> Gson.fromJson(...)`). Scope functions never inspect `T`'s runtime type, so they don't need it.
- Why does `also` use `it` while `apply` uses `this`?Their lambda types differ: `also` takes `(T) -> Unit` (object as parameter `it`); `apply` takes `T.() -> Unit`, a lambda with receiver, so the object becomes `this`.
- Do `also`/`apply` need `reified T`?No. They never inspect T's runtime type. `reified` is only required when the body needs `T::class` or `is T` checks, e.g. JSON deserialization helpers.
apply hands you the object as the room you are standing in (this); also hands it to you on a tray labelled it. Either way you give the same object back.
saying these in an interview costs you the question
- Saying `apply` returns the lambda result (it returns the receiver)
- Swapping which of also/apply uses `it` vs `this`
- Claiming `also`/`apply` need `reified`
- Thinking these allocate a lambda object (they're inline)
- Believing they can be called on a nullable value without `?.`