skip to content

How Kotlin Does Types

How Kotlin arranges its types: one lattice with Any at the top and Nothing at the bottom, nullability folded into every type, variance declared once at the class, and generics that can be reified in inline functions. It is the orientation you need before any variance or erasure question makes sense.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

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

open as a page

Kotlin 'bakes nullability into the type system' rather than treating null as a runtime accident. What does that mean concretely, and how does the type T differ from T??

level: middleimportance: must knowfreq 80%

basics

~20 s

Every type comes in two forms: T can never hold null, T? can. The compiler tracks which one you have and refuses to call methods on a T? until you prove it is not null, so most NullPointerExceptions are caught at compile time.

open as a page

Junior-friendly but commonly confused: distinguish Unit, Nothing, and Any in Kotlin's type model, and where each one shows up.

level: juniorimportance: should knowfreq 60%

basics

~20 s

Any is the parent of all types. Unit is the type for functions that return nothing useful (like void) and has exactly one value. Nothing means a function never returns at all, such as one that always throws.

open as a page

Kotlin emphasizes declaration-site variance with out and in. At the 'how Kotlin models types' level, what problem does this solve and how does it differ from Java's approach?

level: seniorimportance: should knowfreq 55%

basics

~20 s

Variance decides when a List of a subtype counts as a List of a supertype. Kotlin lets the library author declare this once on the type parameter (out for producers, in for consumers), so every user gets it automatically instead of repeating wildcards at each use.

open as a page

Kotlin advertises 'reified generics' for inline functions, even though the JVM erases generics. At the model level, what does reified give you and what are its boundaries?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Normally a generic type is erased, so you cannot check it at runtime. Marking an inline function's type parameter reified makes the actual type available inside the function, so you can write checks like 'is T' without passing a Class object.

open as a page