skip to content

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%

answer

  1. equivalent behavior -> choose by readability
  2. with(x): standalone, non-null batch -> value
  3. x.run: chaining or nullable (x?.run)
  4. run { } no receiver: scope locals / init val
  5. watch nested this shadowing; keep blocks short

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.

solid answer

~40 s

with and run are behaviorally equivalent (this receiver, return block result), so the choice is stylistic. Prefer with(x) { } when you already hold a non-null x and want a readable 'with this object, perform...' grouping, especially when there's no chain. Prefer x.run { } when you're in a call chain (it composes left-to-right) or when x is nullable, since x?.run { } gives expression-style null handling that with cannot. Reach for the receiver-less run { } to introduce a small scope for temporaries or to initialize a val from a multi-line computation. Pitfalls: deeply nested this-receiver blocks shadow each other (use labeled this@Type), large run/with blocks hide intent (extract a function), and relying on an incidental last-expression return is fragile.

code

kotlin · 11 lines
kotlin
// chaining + nullable -> run
val label = user?.run { "$name#$id" } ?: "guest"

// standalone batch on non-null -> with
val line = with(order) { "$id x$qty = ${qty * price}" }

// scoping -> receiver-less run
val pool = run {
    val size = Runtime.getRuntime().availableProcessors()
    java.util.concurrent.Executors.newFixedThreadPool(size)
}

go deeper

for a junior

Knows with takes the object and run is an extension, and that both return the block result.

for a middle

Chooses with for non-null batches and x.run for chains/nullables; uses run { } to scope locals.

for a senior

Justifies the choice on readability and null-safety, and flags nested this shadowing and overlong blocks in review.

for a principal

Codifies team conventions and lint expectations so scope-function intent is consistent and call sites stay readable at scale.

## They're equivalent — so decide on style Both `with(x) { }` and `x.run { }` give a **`this` receiver** and return the **block result**. After inlining they compile to the same thing. So the decision is about **readability and ergonomics**, not behavior. ## Prefer with(x) when... - You already have a **non-null** `x` and want a clear, standalone *'with this object, do a batch of things and produce a value'* statement. - There's **no chain** — `with` reads as a top-level verb. ```kotlin val report = with(metrics) { "req=$requests err=$errors p99=$p99" } ``` ## Prefer x.run when... - You're **chaining**: `run` is an extension, so it flows left-to-right with other calls. - `x` is **nullable**: `x?.run { }` is the idiomatic, expression-style null guard (`with` can't do this). ```kotlin val host = config?.run { resolveHost() } ?: "localhost" ``` ## Use receiver-less run { } when... - You want a **local scope** for temporaries without polluting the outer scope, or to initialize a `val` from a multi-step computation: ```kotlin val client = run { val cfg = loadConfig() val creds = loadCreds() HttpClient(cfg, creds) // returned } ``` ## Readability pitfalls to flag in review 1. **Nested `this` shadowing**: nesting `with`/`run` (or with `apply`) makes inner `this` hide the outer one; an unqualified member could resolve to the wrong receiver. Use **`this@OuterType`** or refactor to fewer levels. 2. **Over-long blocks**: a 30-line `with`/`run` buries the returned value and intent — extract a named function. 3. **Incidental returns**: depending on the last line's value (e.g., a builder method that *happens* to return the builder) is brittle; make the returned value explicit. 4. **Wrong tool for the job**: if the goal is to **configure and keep** the object, `apply` states intent better than `run` ending in the object. 5. **`it` vs `this` confusion** when mixing with `let`/`also` nearby — keep a consistent style within a block. ## Bottom line Default to whichever makes the call site read clearly: `with(x)` for a non-null batch operation that yields a value, `x.run` for chains and nullables, plain `run { }` for scoping. Keep blocks short and receivers unambiguous.

  • Two nested with blocks both define a property named name. How do you disambiguate?
    Use a labeled this, e.g. [email protected], or refactor so the receivers aren't nested; unqualified name resolves to the innermost receiver.
  • Is there any performance reason to prefer one over the other?
    No. Both are inline and compile to equivalent code, so the choice is purely about readability and null-safety ergonomics.

saying these in an interview costs you the question

  • Claiming with is faster or slower than run
  • Nesting many this-receiver blocks without labeled this
  • Writing huge run/with blocks that hide the returned value
  • Using run to 'return the object' instead of the clearer apply

context