skip to content

When should you explicitly declare a function's return type as Nothing, and what concrete benefits does that give callers versus declaring it Unit or omitting the type?

level: seniorimportance: should knowfreq 45%

answer

  1. Nothing = the never-returns contract, compiler-checked
  2. Caller wins: `?:`, when-branch, any expected type
  3. Enables unreachable-code + smart casts
  4. Unit/omitted = compiler assumes control continues
  5. exitProcess and TODO are already : Nothing

basics

~20 s

Declare Nothing when a function never returns — it only throws or loops. This lets callers use it after Elvis, in when-branches, and as any value, and tells the compiler the code after it is dead.

solid answer

~50 s

Declare `: Nothing` on helper functions that always exit abnormally — custom error throwers, `fun fail(...): Nothing`, request-abort helpers, or process-exit wrappers. The payoff is in the **caller's** type checking: a Nothing return lets the call appear on the right of `?:`, in a `when` branch that must produce a value, or as any expected type, without the caller wrapping it in `throw`. It also enables **exhaustiveness and unreachable-code analysis** and **smart casts** on the surviving path. If you instead return `Unit` (or omit the type), the compiler thinks control continues, so `val x = a ?: myFail()` fails to type-check and dead code after the call is not detected. Caveat: only declare Nothing if the body genuinely never returns — the compiler enforces this by requiring every path to throw/loop. Don't overuse it for functions that merely *usually* throw.

code

kotlin · 8 lines
kotlin
// Reusable domain failure that callers can use as any type
fun unsupported(op: String): Nothing =
    throw UnsupportedOperationException(op)

fun area(shape: Shape): Double = when (shape) {
    is Circle -> Math.PI * shape.r * shape.r
    is Line   -> unsupported("line has no area")  // Nothing fits Double
}

go deeper

for a junior

Knows Nothing is for throw/loop helpers but may not articulate caller benefits.

for a middle

Lists Elvis/when usage and that Unit can't substitute.

for a senior

Enumerates the concrete caller benefits and the compiler-checked never-returns constraint, and avoids overuse.

for a principal

Weighs API-design trade-offs: when a Nothing helper improves call-site ergonomics vs. when a plain throw is clearer, and how it interacts with exhaustiveness.

## When to choose Nothing explicitly Use `: Nothing` when the function **cannot complete normally** and you want callers to benefit from that fact: - Custom error helpers: `fun fail(msg: String): Nothing = throw DomainException(msg)`. - Abort/exit wrappers: `fun abort(): Nothing { Runtime.getRuntime().exit(1); throw AssertionError() }` (or wrapping `kotlin.system.exitProcess`, which is already `: Nothing`). - Sealed-`when` fallbacks that should never be hit: `else -> error("unreachable")`. ```kotlin fun fail(msg: String): Nothing = throw IllegalStateException(msg) fun colorOf(state: State): Color = when (state) { State.OK -> Color.GREEN State.WARN -> Color.YELLOW State.ERR -> fail("err has no color") // Nothing fits Color } ``` ## Concrete caller benefits vs Unit / omitted type 1. **Fits any expected type.** Nothing is the bottom type, so `a ?: fail(...)`, a `when` branch, or `val x: Foo = fail()` all type-check. A `Unit` helper cannot do this — `val x: String = a ?: unitFail()` is a type error. 2. **Unreachable-code detection.** After an unconditional Nothing call the compiler warns about dead code; with Unit it silently continues. 3. **Smart casts / definite nullness.** On the surviving branch the compiler narrows types (e.g., non-null after `?: fail()`). 4. **Self-documenting contract.** The signature states "this never returns," which readers and tools rely on. ## Why Unit is wrong here A `Unit` return means "completes, yields no value." The compiler assumes the next statement runs, so none of the four benefits apply. Omitting the type infers Unit, so it is the same mistake. ## Correctness constraint The compiler **verifies** a Nothing function never returns: every path must `throw`, loop forever, or call another Nothing function. If any path could fall through, it will not compile as Nothing. So Nothing is a *checked* promise, not just documentation. ## Anti-patterns - Declaring Nothing on a function that sometimes returns (won't compile, or you'll hack around it). - Using Nothing as a parameter type to mean "no valid argument" — technically possible but confusing. - Reaching for Nothing where a plain exception suffices and no caller needs the type benefit. ## Key terms - **Bottom type**: subtype of all types, here exploited so the call fits any context. - **Exhaustiveness**: a `when` covering all cases; a Nothing branch can fill an impossible case. - **Checked promise**: the compiler enforces the never-returns guarantee.

  • What happens if a function declared `: Nothing` has a path that falls through without throwing?
    It won't compile — the compiler requires every path to never return (throw, infinite loop, or another Nothing call).
  • Why can't a Unit-returning helper be used on the right side of Elvis to get a non-null value?
    Unit means it returns; the Elvis result type would include Unit, so `val x: String = a ?: unitFn()` is a type mismatch.

saying these in an interview costs you the question

  • Declaring Nothing on functions that can return normally
  • Believing Unit gives the same caller benefits as Nothing
  • Not knowing the never-returns guarantee is compiler-enforced
  • Using Nothing purely as documentation without understanding type effects
  • Overusing Nothing where a thrown exception in statement position suffices

context