How is the Elvis operator used with return and throw for early-exit null guards, and why does it work?
answer
- right side can be Nothing
- return/throw/break/continue type as Nothing
- Nothing is subtype of all types
- smart-cast to non-null after guard
- flattens nested null checks
basics
~10 sYou 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 sThe 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 linesfun 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
Recognizes the x ?: return/?: throw idiom and that it exits early on null.
Explains the Nothing type as the reason it type-checks and the resulting smart-cast.
Chooses return vs throw deliberately, uses guard clauses to flatten control flow, and knows the stdlib alternatives.
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)