skip to content

How is the Elvis operator used with return and throw for early-exit null guards, and why does it work?

level: middleimportance: must knowfreq 72%

answer

  1. right side can be Nothing
  2. return/throw/break/continue type as Nothing
  3. Nothing is subtype of all types
  4. smart-cast to non-null after guard
  5. flattens nested null checks

basics

~10 s

You can put return or throw on the right side of ?:. If the left is null, the function returns early or throws; otherwise execution continues with the non-null value.

solid answer

~40 s

The right operand of `?:` can be any expression of type `Nothing` — including `return`, `return x`, `throw`, and `continue`/`break`. Because `Nothing` is a subtype of every type, `value ?: return` type-checks: if `value` is null the function exits; if not, the expression yields the non-null value, which the compiler **smart-casts** for the rest of the scope. This gives a concise fail-fast guard: `val id = req.id ?: return null` or `val user = repo.find(id) ?: throw NotFoundException(id)`. It flattens nested null checks into a linear, readable sequence of guard clauses, much like early returns. After the guard, the variable is non-null with no `!!` needed.

code

kotlin · 6 lines
kotlin
fun authToken(headers: Map<String, String>): String {
    val raw = headers["Authorization"] ?: throw SecurityException("missing auth")
    val token = raw.removePrefix("Bearer ").ifBlank { null }
        ?: throw SecurityException("empty token")
    return token // token is non-null String here
}

go deeper

for a junior

Recognizes the x ?: return/?: throw idiom and that it exits early on null.

for a middle

Explains the Nothing type as the reason it type-checks and the resulting smart-cast.

for a senior

Chooses return vs throw deliberately, uses guard clauses to flatten control flow, and knows the stdlib alternatives.

for a principal

Defines team conventions for null guards (custom exceptions, logging in run blocks) and error-handling consistency across modules.

## The pattern Kotlin lets you put an expression that **never produces a value** on the right side of Elvis. The most common are: ```kotlin val id = request.id ?: return // exit the function, no value val id = request.id ?: return null // exit returning null val id = request.id ?: throw IllegalArgumentException("id required") ``` If the left side is null, control leaves immediately (returns or throws). If the left side is non-null, the variable `id` is assigned the non-null value. ## Why it type-checks: the Nothing type `return`, `throw`, `break`, and `continue` are **expressions of type `Nothing`** in Kotlin. `Nothing` is the bottom type — it has **no instances** and is a **subtype of every other type**. So in `val id: String = request.id ?: return`, the right side has type `Nothing`, which is assignable to `String`. The compiler is satisfied that the declared type can be produced on both branches, while knowing the `Nothing` branch never actually yields a value. ## Smart-cast after the guard Because the only way to reach the next line is for the left side to have been non-null, the compiler **smart-casts** the value to its non-null type for the remainder of the scope: ```kotlin fun process(input: String?) { val s = input ?: return // after this line, s: String (non-null) println(s.length) // no ?. or !! needed } ``` ## Guard clauses vs. nested checks This flattens the classic "pyramid of doom": ```kotlin // Instead of nested if (a != null) { if (b != null) { ... } } fun handle(a: A?, b: B?): Result { val a1 = a ?: return Result.Invalid val b1 = b ?: return Result.Invalid return Result.Ok(a1, b1) } ``` ## throw vs return choice - Use `?: return`/`?: return default` when null is an **expected, recoverable** case. - Use `?: throw SomeException(...)` when null indicates a **programming error or violated precondition** — it documents intent and fails fast. `require`/`requireNotNull` and `checkNotNull` are stdlib alternatives that also throw, but Elvis-throw lets you supply a custom exception type/message inline. ## Multi-line right side with run If you need several statements before exiting, wrap them so the block ends in `Nothing`: ```kotlin val token = header ?: run { log.warn("missing auth header") return null } ```

  • Why can you write `x ?: return` when the variable type is String?
    `return` has type `Nothing`, the bottom type and a subtype of every type, so it is assignable wherever a String is expected.
  • What happens to the variable's type after `val s = x ?: return`?
    It is smart-cast to the non-null type for the rest of the scope, so no `!!` or `?.` is needed.
  • When prefer `?: throw` over `?: return null`?
    When null is an unexpected precondition violation you want to fail fast on, rather than a normal recoverable case.

A bouncer at the door: no valid ticket and you're sent away immediately; otherwise you walk straight in.

saying these in an interview costs you the question

  • Not knowing why return/throw are allowed on the right (Nothing type)
  • Thinking you still need !! after the guard
  • Confusing the early-exit with returning the null
  • Using ?: throw for ordinary expected-null cases (should be a default)

context