skip to content

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