skip to content

What is the type of the literal `null` in Kotlin, and how do `Nothing`, `Nothing?`, `Any`, and `Any?` fit into the type hierarchy with respect to nullability?

level: seniorimportance: should knowfreq 35%

answer

  1. `null : Nothing?` — that's its actual type
  2. `Nothing` = uninhabited, subtype of every type
  3. `Nothing?` holds exactly one value: null
  4. `Any?` top of all; `Any` top of non-null
  5. `throw`/`TODO()`/`return` are `Nothing`-typed

basics

~20 s

null's type is Nothing?. Nothing is the empty type at the bottom of every type; Nothing? holds only null. Any? sits at the very top (all values including null); Any is the top of all non-null values.

solid answer

~40 s

Kotlin's type lattice has `Any?` as the absolute top — supertype of everything, including `null`. Just below it, `Any` is the top of all **non-null** types. At the bottom, `Nothing` is the **uninhabited** type (no values; a subtype of every type), used for expressions that never return — `throw`, infinite loops, `TODO()`. Its nullable form `Nothing?` is inhabited by exactly one value: `null`. So the literal `null` has type `Nothing?`, which is a subtype of every nullable type `T?` — that's *why* `null` can be assigned to any nullable variable (`String?`, `List<Int>?`, etc.). The hierarchy is two parallel cones joined at the top: non-null types under `Any`, their nullable counterparts under `Any?`, with `Nothing?` as the single bottom of the nullable side.

code

kotlin · 8 lines
kotlin
fun fail(m: String): Nothing = throw IllegalStateException(m)

val raw: String? = readLine()
// Elvis right side is Nothing -> result stays String, not String?
val name: String = raw ?: fail("missing")

val inferred = null          // type is Nothing?
val ok: List<Int>? = null    // Nothing? <: List<Int>?

go deeper

for a junior

May only know Any?/Any roughly; not expected to recall Nothing? precisely.

for a middle

Knows Any? is the top and includes null, Any excludes it; vague on the bottom type.

for a senior

States null : Nothing?, explains Nothing as the uninhabited bottom subtype of all types, and why throw keeps Elvis results non-null.

for a principal

Uses the lattice to reason about inference, generics variance, and how Nothing enables precise control-flow/exhaustiveness typing.

## The two ends of the lattice Kotlin's whole type system is bounded top and bottom. - **`Any?`** — the **top type**. Every type, nullable or not, is a subtype of `Any?`. A variable of type `Any?` can hold *any* value, including `null`. - **`Any`** — the top of all **non-nullable** types. It is `Any?` minus `null`. Every non-null type is a subtype of `Any`. - **`Nothing`** — the **bottom type**, also called the *uninhabited* type: it has **no instances at all**. It is a subtype of *every* type. You can't create a `Nothing`; you can only have an expression *typed* `Nothing` because it never produces a value. - **`Nothing?`** — `Nothing` plus `null`, so it is inhabited by **exactly one value: `null`**. ## The type of `null` The literal `null` has type **`Nothing?`**. Because `Nothing?` is a subtype of every nullable type, `null` is assignable to `String?`, `List<Int>?`, `Map<K, V>?`, and so on — that subtyping is the formal reason null "fits everywhere a nullable is allowed". ```kotlin val n = null // inferred type: Nothing? val s: String? = null // Nothing? <: String? -> OK ``` ## Why `Nothing` exists (and how it interacts with nullability) `Nothing` types expressions that **never return normally**: ```kotlin fun fail(msg: String): Nothing = throw IllegalStateException(msg) val name: String = user.name ?: fail("no name") // ^ returns Nothing, so the ?: still // type-checks to String, not String? ``` Because `Nothing` is a subtype of `String`, the Elvis branch that calls `fail(...)` doesn't widen the result to nullable — the compiler knows that branch can't yield a value. `throw`, `return`, `break`, `continue`, `TODO()`, and infinite `while (true)` loops are all `Nothing`-typed. This is what lets smart casting and exhaustiveness reasoning stay precise. ## Inference corner case `val x = null` infers `Nothing?` — almost useless on its own, since the only value it can ever hold is `null`. You normally annotate (`val x: String? = null`) so the variable can later hold real strings. ## Mental model Picture two cones meeting at `Any?`: ``` Any? <- top: everything incl. null / \ Any (nullable types T?) | | (non-null T) | \ | Nothing Nothing? <- Nothing?: only null \ / Nothing <- bottom: no values ``` `Nothing` is below everything; `Any?` is above everything; `null : Nothing?` slots in as the lone inhabitant of the nullable bottom.

  • Why does `val x = user.name ?: throw ...` keep type `String` instead of `String?`?
    `throw` has type `Nothing`, the bottom type, which is a subtype of `String`. So the Elvis result's least upper bound stays `String`, not `String?`.
  • What does `val x = null` infer as, and why is that rarely useful?
    `Nothing?`. Its only inhabitant is `null`, so the variable can never hold anything else — you normally annotate a real nullable type instead.

Nothing is an empty room every building contains by definition; Any? is the open sky over all buildings. null is the single ghost that lives only in the empty room with a door (Nothing?).

saying these in an interview costs you the question

  • Saying `null` has type `Any?` or `Unit`
  • Confusing `Nothing` (no values) with `Unit` (one value, the procedure return)
  • Claiming `Nothing` can be instantiated
  • Not knowing `Any` excludes null while `Any?` includes it

context