skip to content

Kotlin's type system is often described as a single lattice with a top type and a bottom type. What are those two types, and what does each one mean in practice?

level: juniorimportance: must knowfreq 70%

answer

  1. Any = top, Nothing = bottom
  2. Any? is the real top of the whole lattice
  3. Nothing has zero instances, subtype of everything
  4. throw / exitProcess / infinite loop : Nothing
  5. null literal has type Nothing?

basics

~20 s

Any is the top type: every non-null value is an Any, so it is the common parent of all types. Nothing is the bottom type: it has no values and means code never returns normally there.

solid answer

~40 s

Kotlin arranges all types into one lattice. At the top is Any (and its nullable form Any?), the supertype of everything; in Java terms it is roughly Object, but with equals/hashCode/toString only. At the very bottom is Nothing, a type with no instances that is a subtype of every other type. Because Nothing fits anywhere, expressions like throw and functions that loop forever or call exitProcess have type Nothing, which lets the compiler treat them as valid in any branch (val x: String = something ?: throw ...). Any? is the top of the whole lattice including nullables; Nothing? (the type of the literal null) sits just above the absolute bottom. Knowing these endpoints explains why throw works as an expression and why a when over a sealed type can be exhaustive.

code

kotlin · 4 lines
kotlin
fun required(field: String?): String =
    field ?: throw IllegalArgumentException("missing")  // throw : Nothing

fun loopForever(): Nothing { while (true) { /* ... */ } }

go deeper

for a junior

Names Any as top and Nothing as bottom and gives a basic sense of each.

for a middle

Explains Nothing has no instances and is a subtype of everything, and that throw/exitProcess have type Nothing.

for a senior

Distinguishes Any vs Any? and Nothing vs Nothing?, and ties Nothing to control-flow analysis (unreachable code, Elvis return types).

for a principal

Frames the lattice as the foundation that makes null-safety, exhaustiveness, and smart-cast flow analysis uniform rather than special cases.

## The idea of a type lattice A *lattice* is just an ordering where every pair of elements has a single common ancestor (a *top*) and a single common descendant (a *bottom*). Kotlin organizes all of its types into one such lattice connected by the *subtype* relation: `B` is a subtype of `A` if a `B` value can be used wherever an `A` is expected. ## `Any` — the top type `Any` is the supertype of every non-nullable type. Every class you declare implicitly extends `Any`, much like `Object` in Java. But `Any` only declares three members: `equals()`, `hashCode()`, and `toString()`. It does **not** have Java's `wait`/`notify`/`getClass`. ```kotlin val values: List<Any> = listOf(1, "text", 3.14, true) ``` The true top of the *entire* lattice (including nullable types) is `Any?`, because a nullable value is not an `Any` (which excludes `null`). ## `Nothing` — the bottom type `Nothing` is a type with **no values at all** — you can never hold a `Nothing`. It is a subtype of *every* type. Because it fits anywhere, the compiler gives `Nothing` to expressions that never produce a value and never return normally: - `throw SomeException()` has type `Nothing`. - A function that always throws or calls `exitProcess(...)` can be declared to return `Nothing`. - An infinite loop region has type `Nothing`. ```kotlin fun fail(msg: String): Nothing = throw IllegalStateException(msg) val name: String = user.name ?: fail("name required") // ?: branch is Nothing, fits String ``` Because `fail` returns `Nothing`, the compiler knows that after the `?:` only the left side can survive, so the result is a non-null `String`. The compiler also marks code after a `Nothing` expression as **unreachable**. ## `Nothing?` The literal `null` has the type `Nothing?` — the bottom of the nullable side. It is assignable to any nullable type, which is why `null` can initialize any `T?`. ## Why this matters A single unified lattice with these endpoints is what makes Kotlin's null handling, exhaustiveness checking, and control-flow analysis (smart casts, unreachable code) work coherently rather than as bolted-on special cases.

  • Why can throw be used on the right side of an Elvis operator that produces a non-null String?
    throw has type Nothing, which is a subtype of String, so the Elvis result type stays String (non-null); the throw branch contributes no nullability.
  • Is Any the same as Java's Object?
    Roughly the top type, but Any only exposes equals/hashCode/toString; Java's Object methods like wait/notify/getClass are not on Any.

Any is the universal container everything fits inside; Nothing is an empty box that, being empty, can be slotted into any shelf.

saying these in an interview costs you the question

  • Saying Nothing is the same as Unit (Unit has exactly one value; Nothing has none)
  • Claiming Any includes null values (it does not; Any? does)
  • Thinking Nothing is a runtime class you can instantiate
  • Confusing top and bottom (Any at top, Nothing at bottom)

context