skip to content

Explain the x?.let { } idiom. What exactly happens when x is null versus non-null?

level: middleimportance: must knowfreq 85%

answer

  1. ?. short-circuits to null; block runs only when non-null
  2. it is smart-cast to the non-null type inside
  3. Whole expression type is R? (nullable)
  4. Pairs with ?: for a fallback
  5. Works where var smart-cast fails

basics

~10 s

x?.let { } runs the block only when x is not null. If x is null, the block is skipped and the whole expression is null. Inside the block x is non-null.

solid answer

~50 s

`?.` is the safe-call operator: `x?.let { }` calls `let` only if `x != null`. When `x` is non-null, the block runs with the non-null value as `it` (smart-cast from `T?` to `T`), and the expression evaluates to the lambda result. When `x` is null, `let` is never invoked and the whole expression short-circuits to `null` — the block body never executes (so any side effects inside are skipped too). The result type is `R?`. This is the idiomatic Kotlin replacement for `if (x != null) { ... }` when you also want to capture/transform the value. Combine with `?:` (Elvis) to provide a fallback: `x?.let { transform(it) } ?: default`. A common gotcha: if the block itself can return null, you cannot distinguish 'x was null' from 'block returned null' by the result alone.

code

kotlin · 5 lines
kotlin
fun greet(name: String?): String =
    name?.let { "Hello, ${it.trim()}!" } ?: "Hello, stranger!"

println(greet("  Ada ")) // Hello, Ada!
println(greet(null))      // Hello, stranger!

go deeper

for a junior

Knows ?.let runs the block only when the value is non-null.

for a middle

Explains short-circuiting, the R? result type, smart-cast of it, and pairing with ?: for fallback.

for a senior

Cites the mutable-var smart-cast case and the null-ambiguity gotcha; weighs ?.let vs a plain if.

for a principal

Sets guidance on readability tradeoffs and avoiding deeply nested ?.let chains in team code.

## The pieces - **`?.` (safe-call operator):** `a?.b` evaluates to `null` if `a` is null, otherwise to `a.b`. It short-circuits. - **`let`:** passes its receiver in as `it` and returns the lambda result. Combining them, `x?.let { block }` means: *if `x` is not null, run `block` with `x` as `it`; otherwise produce `null`.* ## Step by step ```kotlin val name: String? = readNullable() val length: Int? = name?.let { it.length } ``` - If `name` is **non-null**: `let` is called, `it` has type **`String`** (smart-cast — the `?` is gone inside the block), and the result is the length (`Int`), so `length` is that `Int`. - If `name` is **null**: `?.` short-circuits, `let` is **never called**, the block (and any side effects) does not run, and `length` is `null`. - The overall type is **`R?`** (here `Int?`). ## Why use it over `if (x != null)` - It scopes the non-null value to a **fresh local** (`it`), avoiding repeated smart-cast issues — especially for **mutable `var` properties**, which cannot be smart-cast directly (`if (member != null) member.foo()` fails to compile for a `var` member, but `member?.let { it.foo() }` works because `it` is a stable `val`). - It chains fluently with `?:` and other calls. ```kotlin class C(var data: String?) { fun render() = data?.let { it.uppercase() } ?: "<none>" } ``` ## Gotchas - **Null ambiguity:** if the block can itself yield `null`, the final `null` could mean *either* `x` was null *or* the block returned null. - **Elvis fallback:** `x?.let { ... } ?: fallback` — note the fallback also fires if the block legitimately returns null. - **Don't overuse:** for a plain non-null guard with no captured value, a simple `if` may read better.

  • Why does x?.let work for a mutable var property where if (x != null) x.foo() does not?
    A var can change between the null check and use, so Kotlin won't smart-cast it. let binds the value to it, a stable val, so it's provably non-null inside the block.
  • What is the type of name?.let { it.length } when name is String??
    Int? — the safe call makes the whole expression nullable even though it inside is non-null.

It's a gate that only opens for non-null traffic; nulls are turned away and never enter the block.

saying these in an interview costs you the question

  • Saying the block still runs (with it = null) when x is null
  • Thinking the result type is non-nullable
  • Not knowing it is smart-cast to the non-null type
  • Claiming ?: fallback only fires on null x (it also fires if the block returns null)

context