skip to content

How do you combine ?. with let and also (i.e. ?.let / ?.also) to run a block only when a value is non-null, and how do they differ?

level: middleimportance: should knowfreq 75%

answer

  1. ?.let runs block only when non-null
  2. it is the non-null value inside
  3. let returns block result; also returns original
  4. also = side effects (logging), keep value
  5. pair with ?: for a fallback

basics

~20 s

Write value?.let { ... } to run the block only when value is not null. Inside, the value is non-null. ?.let returns the block's result; ?.also returns the original value and is used for side effects like logging.

solid answer

~40 s

Because ?. short-circuits on null, value?.let { block } runs the lambda only when value is non-null, and inside the lambda the receiver is smart-cast to the non-null type, exposed as the parameter it (or a named param). let returns the lambda's last expression, so it's used to transform: name?.let { it.uppercase() } yields String?. also is the side-effect twin: it passes the value as it but returns the original value, so user?.also { log(it) } returns the user unchanged. The whole expression stays nullable (T?) because the receiver may be null. A frequent idiom is value?.let { ... } ?: default to provide a fallback. Don't confuse let (param it, returns result) with run/apply (receiver this).

code

kotlin · 3 lines
kotlin
val label = name?.let { "User: $it" } ?: "Anonymous"
val saved = user?.also { log.info("id=${it.id}") }
// let -> transformed String; also -> same user (or null)

go deeper

for a junior

Can write value?.let { use it } to run a block when non-null.

for a middle

Distinguishes let (returns result) from also (returns receiver) and knows it is smart-cast non-null.

for a senior

Explains that conditionality comes from ?., not the scope function, and picks the right scope function deliberately.

for a principal

Sets team guidance on scope-function usage to avoid nesting/readability problems and keep null-handling idiomatic.

## Combining `?.` with `let` and `also` `let` and `also` are **scope functions** from the standard library. Combined with `?.`, they let you run a block **only when a value is non-null**, because the safe call short-circuits the whole call when the receiver is null. ### `?.let` — transform / conditional block `let` takes the receiver as a lambda **argument** (`it` by default, or a named parameter) and **returns the lambda's result** (its last expression). ```kotlin val name: String? = fetchName() val upper: String? = name?.let { it.uppercase() } // block runs only if name != null; inside, it: String (non-null) // if name == null -> whole expression is null, block skipped ``` Inside the block, `it` is the **non-null** type (`String`, not `String?`), so you can use it directly. The expression's type is still nullable (`String?`) because `name` might have been null. With Elvis to supply a fallback: ```kotlin val label = name?.let { "User: $it" } ?: "Anonymous" ``` ### `?.also` — side effect, keep the value `also` also takes the receiver as `it`, but **returns the original receiver**, not the block's result. Use it for side effects (logging, validation, registering) without breaking a chain. ```kotlin val saved: User? = user?.also { logger.info("saving ${it.id}") } // returns the same user (or null); the block's value is ignored ``` ### Key differences | | `let` | `also` | |---|---|---| | Receiver as | `it` (argument) | `it` (argument) | | Returns | lambda result | original receiver | | Use for | transform / map | side effects | Both contrast with `run`/`apply`, which expose the receiver as `this` instead of `it`. Note: a non-null-safe `let` (`value.let { }`) still runs the block even when `value` is null — it's the `?.` that makes the block conditional. So the conditional behavior comes from the **safe call**, not from `let`/`also` themselves.

  • Does user.let { } (without ?.) skip the block when user is null?
    No. Plain .let always runs; only the ?. safe call makes execution conditional on non-null.
  • When would you choose ?.also over ?.let?
    When you want to perform a side effect (log, validate) but return the original value unchanged to continue a chain.

?.let is 'if it exists, transform it'; ?.also is 'if it exists, do something with it but hand it back unchanged'.

saying these in an interview costs you the question

  • Saying let and also both return the lambda result
  • Claiming plain .let skips the block on null
  • Thinking it inside ?.let is still nullable
  • Confusing it (let/also) with this (run/apply)
  • Using ?.let purely for side effects instead of ?.also

context