skip to content

Explain where Nothing and Nothing? sit in Kotlin's type lattice, and how that bottom position interacts with nullable types during type unification.

level: seniorimportance: should knowfreq 50%

answer

  1. Nothing = bottom, subtype of all, no values
  2. Nothing? only value is null, below every T?
  3. null literal has type Nothing?
  4. LUB(T, Nothing)=T; LUB(T, Nothing?)=T?
  5. throw is Nothing (non-null); null is Nothing? (nullable)

basics

~20 s

Nothing is the bottom type: it is a subtype of every type and has no values. Nothing? is its nullable form, sitting just above Nothing, and is a subtype of every nullable type T?. Together they anchor the bottom of the lattice.

solid answer

~40 s

`Nothing` is the **bottom type**: a subtype of *every* type (`Nothing <: T` for all T), with no instances — an expression of type `Nothing` never returns normally (e.g. `throw`, `TODO()`, infinite loop). `Nothing?` is its nullable counterpart; its only value is `null`, and it is a subtype of every **nullable** type (`Nothing? <: T?`). In the lattice, `Nothing <: Nothing? <: T?` and `Nothing <: T <: T?`. During unification this makes `Nothing`/`Nothing?` the neutral elements of LUB: `LUB(T, Nothing) = T` and `LUB(T, Nothing?) = T?`. That second identity is exactly why `if (c) value else null` (with `null: Nothing?`) yields `T?`. Because `Nothing` is below everything, a `throw` branch contributes no constraint — `val x: String = if (c) "a" else throw E()` keeps `x` as `String`, not `String?`.

code

kotlin · 7 lines
kotlin
fun fail(): Nothing = throw IllegalStateException()

val a: String = if (c) "x" else fail()   // Nothing branch -> stays String
val b = if (c) "x" else null             // Nothing? branch -> String?

val empty: List<String> = emptyList<Nothing>() // List<Nothing> <: List<String>
val n: String? = null                    // null: Nothing? <: String?

go deeper

for a junior

Knows Nothing means a function never returns normally (throw/TODO) and that null relates to nullable types.

for a middle

States Nothing is the bottom type with no values and Nothing? holds only null, below every T?.

for a senior

Explains LUB neutral-element behavior: throw (Nothing) keeps results non-null while null (Nothing?) makes them nullable, with lattice edges.

for a principal

Connects bottom types to smart-cast soundness after diverging branches, covariant empty containers (List<Nothing>), and overall lattice anchoring.

## What is a bottom type? A **bottom type** is a type that is a subtype of *every* other type and has **no values** at all. In Kotlin this is `Nothing`. Because it is uninhabited, an expression typed `Nothing` can never actually produce a value — control flow must diverge: it `throw`s, loops forever, or calls something like `TODO()` / `error(...)` / `exitProcess(...)` (all declared to return `Nothing`). ```kotlin fun fail(msg: String): Nothing = throw IllegalStateException(msg) ``` ## Two bottom anchors: Nothing and Nothing? Kotlin actually has **two** related bottom anchors because of nullability: - `Nothing` — subtype of every type, including every non-null type and every nullable type. Zero values. - `Nothing?` — the nullable form. Its **only** value is `null`. It is a subtype of every **nullable** type `T?`. The lattice edges: ``` Nothing <: Nothing? (T <: T? applied to Nothing) Nothing <: T <: T? (for any T) Nothing? <: T? (Nothing? is below every nullable type) ``` So `Nothing` is the absolute floor; `Nothing?` is the floor of the *nullable half* of the lattice. ## The type of `null` The literal `null` has type `Nothing?`. That is the precise reason `null` can be assigned to *any* nullable type: `Nothing? <: String?`, `Nothing? <: Int?`, etc. ## Interaction with unification (LUB) Because they are bottom anchors, `Nothing` and `Nothing?` act as **identity / neutral elements** for least-upper-bound: - `LUB(T, Nothing) = T` — a diverging branch adds no upper constraint. - `LUB(T, Nothing?) = T?` — a `null` branch lifts the result into the nullable half. ```kotlin // throw has type Nothing -> does NOT make result nullable val a: String = if (c) "x" else throw RuntimeException() // String // null has type Nothing? -> makes result nullable val b = if (c) "x" else null // String? ``` This is a frequent source of confusion: a `throw`/`return`/`TODO()` branch keeps the result non-null, while a `null` branch makes it nullable — the difference is `Nothing` vs `Nothing?`. ## Why uninhabited matters Since `Nothing` has no values, `Nothing <: T` is vacuously safe: there is never an actual value to violate T's contract. This also powers smart casts after diverging calls — after `val x = y ?: return`, the compiler knows the `return` branch had type `Nothing` and smart-casts `y` to non-null. ## Generics and variance `Nothing` is the natural element for empty containers: `emptyList<Nothing>()` produces a `List<Nothing>`, assignable to `List<String>` because `List` is covariant (`out`) and `Nothing <: String`. ## Summary lattice picture ``` Any? / \ Any ... (other T?) | | T ... \ / Nothing? | Nothing ```

  • Why does a `throw` branch in an if/else not make the result nullable, but a `null` branch does?
    throw has type Nothing (no values, below everything), so LUB(T, Nothing) = T. null has type Nothing? (holds null, below every T?), so LUB(T, Nothing?) = T?.
  • How does Nothing enable smart casting after `?: return`?
    The right side of ?: is a `return`/`throw` expression of type Nothing; the compiler knows that branch never yields a value, so the left operand must have been non-null, and smart-casts it.

Nothing is like the empty set: it fits inside every set, but you can never pull an element out of it.

saying these in an interview costs you the question

  • Claiming Nothing has a single value (it has none; Nothing? has the value null)
  • Saying a throw branch makes an if/else result nullable
  • Confusing Nothing with Unit (Unit has exactly one value)
  • Thinking Nothing is only a supertype, not the bottom subtype
  • Not knowing the null literal's static type is Nothing?

context