Why would you choose run or with over apply for configuring an object, and what is the trap if you pick the wrong one?
answer
- run/with -> block result; apply -> object
- assignment last line = Unit (the apply trap)
- transform/compute -> run/with
- configure-and-keep -> apply
- run { } with no receiver scopes locals
basics
~20 sUse 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.
solid answer
~40 srun and with return the lambda result; apply returns the receiver object. Choose run/with when the point of the block is to produce a value — a computed string, a parsed result, a different type. Choose apply when you're mutating/configuring an object and want to keep using that same object (builder-style). The classic trap: writing val x = obj.run { prop = a; prop2 = b } expecting obj, but getting Unit because the last statement is an assignment. If you want the configured object back, use apply. Conversely, ending a run block with a meaningful expression is exactly how you map-and-return in one shot. with shares run's return semantics but is a non-extension call; it's typically used to group many calls on one object when you don't need a chain or null safety.
code
kotlin · 10 linesclass StringBuilderDemo {
fun build(): String = StringBuilder().run {
append("a"); append("b")
toString() // returned String -> correct
}
fun buildWrong(): StringBuilder = StringBuilder().run {
append("a") // last expr is StringBuilder from append,
append("b") // works by luck; use apply for clarity
}
}go deeper
Knows run/with return the block result and apply returns the object.
Reliably picks run/with for transforms and apply for configure-and-keep, and recognizes the Unit-from-assignment trap.
Explains the no-receiver run form and warns against relying on incidental return types like append's builder.
Establishes idioms so the team's intent (compute vs configure) is obvious at the call site and code-reviews enforce it.
## The two axes again Scope functions differ by **receiver** (`this` vs `it`) and **return** (object vs block result). `run`, `with`, and `let` return the **block result**; `apply` and `also` return the **object**. `run`/`with`/`apply` all use the **`this`** receiver. So within the `this`-receiver group, the decision is purely about **what you want back**: - Need the **computed value** of the block -> `run` / `with`. - Need the **same object** back (to assign or chain) -> `apply`. ## When run/with shine They return the last expression, making them perfect for **transforming** an object into another value: ```kotlin val host: String = with(uri) { if (port > 0) "$host:$port" else host // computed, returned } ``` or for **initializing a val** from a sequence of member calls whose final line is the result. ## The apply trap If you intend to configure-and-keep an object but use `run`: ```kotlin val p = Person().run { name = "Ada" age = 36 // last statement is an assignment } // p is Unit! Not the Person. ``` Assignments evaluate to **`Unit`**, so the block result is `Unit`. The compiler will usually complain (or infer `p: Unit`), but the bug is conceptual: you wanted the object. The fix is `apply`, which returns the receiver: ```kotlin val p = Person().apply { name = "Ada" age = 36 } // p is the Person ``` ## The reverse trap Using `apply` when you actually need a computed value forces an awkward extra step, because `apply` discards the block result and hands back the object. Reach for `run`/`with` there. ## run as a no-receiver block `run { ... }` can also be called with **no object** as a plain expression block — handy to scope locals or initialize a `val` with a small computation: ```kotlin val config = run { val raw = loadRaw() parse(raw) // returned } ``` ## Choosing run vs with Within the return-block-result group, prefer `x.run { }` when `x` may be null (`x?.run`) or when you're already chaining; prefer `with(x) { }` for a standalone 'operate on this object' statement.
- Your run block's last line is append("x"), which returns the StringBuilder. Does that give you the object back?Yes, but only because append happens to return the builder. It's fragile and misleading; use apply to express 'return the object' intent explicitly.
- Can run be used with no receiver at all?Yes. run { ... } as a standalone call runs a block and returns its result, useful for scoping temporaries or initializing a val from a multi-line computation.
saying these in an interview costs you the question
- Saying apply and run are interchangeable
- Not knowing assignments produce Unit, causing the apply trap
- Using apply then awkwardly extracting a computed value
- Claiming run cannot be called without a receiver