skip to content

?.let Idiom and Chaining

x?.let { } runs a block only when x is non-null, binding the smart-cast value as it. Interviewers care about the chaining question: when a let chain reads better than nested ifs, and when it turns into noise.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

What does `value?.let { ... }` do, and what is `it` inside the block?

level: juniorimportance: must knowfreq 80%

answer

  1. Block runs only on non-null
  2. `it` = the non-null value (smart-cast)
  3. `?.` short-circuits to null, lambda skipped
  4. Returns the lambda result (it's an expression)
  5. Best for nullable `var`/properties

basics

~10 s

It runs the block only when value is not null. Inside the block, it is that same value, now guaranteed non-null, so you can use it safely without extra null checks.

solid answer

~40 s

`?.let { }` combines the safe-call operator `?.` with the standard-library scope function `let`. If the receiver is `null`, the whole `?.let { }` expression short-circuits to `null` and the block never runs. If it is non-null, `let` invokes the lambda with the value as its single argument, conventionally named `it`, smart-cast to the non-null type. It returns whatever the lambda's last expression produces (so it's an expression, not just a statement). It's the idiomatic way to run code on a nullable only when present, e.g. `name?.let { println(it.length) }`, replacing an `if (name != null)` block — especially useful for nullable `var` properties where a plain smart cast isn't allowed.

code

kotlin · 5 lines
kotlin
val name: String? = readLine()
val len: Int? = name?.let {
    println("got: $it")
    it.length            // returned to len
}

go deeper

for a junior

Knows the block runs only on non-null and it is the value; can replace an if (x != null) with it.

for a middle

Explains the smart-cast of it, the return value, and why it's needed for mutable var properties.

for a senior

Frames it as ?. + the let scope function, contrasts with also/apply/run, and discusses readability trade-offs.

for a principal

Reasons about when the idiom hurts readability vs. an early-return guard, and team conventions for naming it in chains.

## What `?.let { }` is This idiom is built from two pieces: - **`?.` — the safe-call operator.** `a?.b` evaluates to `null` if `a` is `null`; otherwise it evaluates `a.b`. It never throws a `NullPointerException` for the `a`-is-null case. - **`let` — a scope function** from the Kotlin standard library. `let` is an extension function `fun <T, R> T.let(block: (T) -> R): R`. It takes the receiver, passes it to `block` as an argument, and returns whatever `block` returns. Combined, `value?.let { ... }`: 1. If `value` is `null`, the `?.` short-circuits and the lambda is **never executed**; the expression's value is `null`. 2. If `value` is non-null, `let` runs the lambda with the value bound to **`it`**, which is **smart-cast to the non-null type** (e.g. `String?` becomes `String`). ## Why `it` `let` passes its receiver as a **lambda argument**, so inside you refer to it via the implicit single-parameter name `it`. You can rename it for clarity: `value?.let { v -> ... }`. ## Why use it instead of `if (value != null)` For a **local `val`**, a plain `if (value != null) { value.foo() }` already smart-casts. But for a **mutable `var`** (especially a property that another thread could change), the compiler refuses to smart-cast because the value might change between the check and the use. `?.let` captures the value once as a stable parameter, so it's both safe and idiomatic. ```kotlin class Config { var timeout: Int? = null } fun show(c: Config) { // c.timeout?.foo() would not smart-cast (mutable property) c.timeout?.let { t -> println("timeout is $t") // t: Int (non-null) } } ``` ## Return value Because `let` returns the lambda result, `?.let` is an **expression**: ```kotlin val upper: String? = name?.let { it.uppercase() } ``` If `name` is null, `upper` is null; otherwise it is the uppercased name. ## Key APIs/keywords named `?.` (safe call), `let`, `it`, smart cast, scope function, Elvis `?:` (often paired to supply a fallback).

  • If `name` is null in `name?.let { it.length }`, what is the result?
    `null` — the safe call short-circuits and the lambda never runs, so the whole expression is `null`.
  • Can you rename `it`?
    Yes: `name?.let { n -> n.length }`. Naming the parameter improves readability, especially in nested lets.

Like 'if there's a letter in the mailbox, open and read it' — no letter, nothing happens.

saying these in an interview costs you the question

  • Saying the lambda always runs even when the receiver is null
  • Thinking `it` is still nullable inside the block
  • Claiming `?.let` returns the receiver (that's `also`, not `let`)
  • Confusing `let` with `apply`/`run` (which use `this`, not `it`)

context

open as a page

How do you safely access deeply nested nullable properties using `?.`, `?:`, and `?.let`? Show how short-circuiting works across the chain.

level: middleimportance: must knowfreq 70%

basics

~20 s

Chain ?. between each nullable step; if any link is null the whole chain becomes null and stops early. Add ?: at the end for a default, or ?.let { } to run code only when the final value exists.

open as a page

When is `x?.let { }` strictly better than `if (x != null) { }`, and what is one subtle gotcha when `x?.let` is paired with `?:`?

level: middleimportance: should knowfreq 65%

basics

~20 s

?.let is better when x is a mutable property the compiler won't smart-cast. The gotcha: if you write x?.let { ... } ?: fallback, the fallback also runs when the lambda itself returns null — not only when x is null.

open as a page

Compare `?.let` with `?.also`, `?.run`, and `?.apply` for null-guarded code. When would you pick each, and what does each return?

level: seniorimportance: should knowfreq 55%

basics

~20 s

All four run only on non-null receivers. let/also pass the value as it; run/apply expose it as this. let/run return the block's result; also/apply return the original receiver. Pick by what you need bound and what you want back.

open as a page

What are the readability and correctness pitfalls of `?.let` in nested blocks, loops, and side-effecting code? How would you refactor an over-nested `let` chain?

level: seniorimportance: nice to knowfreq 40%

basics

~20 s

Deeply nested lets become a pyramid that's hard to read; a return inside one returns from the function, not the block; and ?.let swallows the null case silently. Often a guard clause with early returns or destructuring reads better.

open as a page