Nothing & Any Explained

KOTLIN › Types & Classes

Every type system has a top and a bottom. In Kotlin, the **top** is Any -- every non-nullable type is a subtype of Any. And at the very bottom sits Nothing, a type that has **zero instances**.

Why does that matter? Because the compiler uses both of them constantly, even when you never write them yourself.

Any is the root of all non-nullable types:

val x: Any = "hello"  // String <: Any
val y: Any = 42        // Int <: Any
val z: Any = listOf(1) // List<Int> <: Any

But Any is **non-nullable**, so null does not fit:

// val a: Any = null  // compile error
val b: Any? = null     // OK: Any? accepts null

Any? -- nullable Any -- is the **true top** of the entire hierarchy. Every type, nullable or not, is a subtype of Any?.

Kotlin's Any maps to java.lang.Object on the JVM, but in Kotlin source it only declares three members: equals(), hashCode(), and toString(). Java's extras like clone() and wait() are not part of Any's API.

Now for the strange one. Nothing is the **bottom type**: it is a subtype of every other type, and you can never create an instance of it.

That sounds useless, but it is exactly what makes throw work as an **expression**. In Kotlin, throw is not a statement -- it has a type, and that type is Nothing:

val name: String = input ?: throw IllegalArgumentException("required")

Why does this compile? The elvis operator ?: needs both sides to produce a compatible type. The left side is String (the non-null branch). The right side is throw, which has type Nothing. And Nothing is a subtype of String, so the overall expression types as String.

This is the key insight: **Nothing being a subtype of everything is what lets throw appear anywhere an expression is expected.** Without that, you would need awkward workarounds every time you wanted to throw inside a when, an elvis, or a single-expression function.

TODO() is probably the most common Nothing-typed function you will use. It always throws NotImplementedError, so it never returns:

public inline fun TODO(reason: String): Nothing =
    throw NotImplementedError("An operation is not implemented: $reason")

Because it returns Nothing, the compiler lets it stand in for **any** return type:

fun calculateTax(income: Double): Double {
    TODO("tax calculation not yet implemented")
    // Compiles: Nothing <: Double
}

The standard library has a few other Nothing functions:

- error(message) -- throws IllegalStateException - throw itself -- the expression, not a function, but typed Nothing

These are all the same pattern: a function that never completes normally, typed Nothing so it can appear in any expression context.

fun requirePositive(n: Int): Int =
    if (n > 0) n else error("must be positive: $n")
// error() returns Nothing, which <: Int, so both branches unify to Int

Here is where Nothing really earns its keep: **when expressions and sealed hierarchies.**

When when is used as an expression (assigned to a variable or returned), every branch must produce a value of the same type. A throw branch returns Nothing, which is a subtype of whatever the other branches return:

sealed class Result {
    data class Ok(val data: String) : Result()
    data class Err(val msg: String) : Result()
}

fun unwrap(r: Result): String = when (r) {
    is Result.Ok  -> r.data          // String
    is Result.Err -> error(r.msg)    // Nothing <: String
}

The Ok branch returns String. The Err branch calls error(), which returns Nothing. The compiler unifies String and Nothing to String -- because Nothing is a subtype of String.

This is why Kotlin's error-handling patterns feel so clean: you can throw or error() in any branch of a when without breaking the expression's type.

You can write a function with return type Nothing that does **not** throw -- it just never returns:

fun eventLoop(): Nothing {
    while (true) {
        val event = waitForEvent()
        handleEvent(event)
    }
}

This infinite loop never completes, so Nothing is the correct return type. The compiler can use this information: any code after a call to eventLoop() is **dead code** and the compiler will warn you about it.

fun main() {
    eventLoop()
    println("done")  // warning: unreachable code
}

Nothing means "this function never returns normally" -- whether because it throws, loops forever, or calls another Nothing-typed function like exitProcess(). The specific mechanism does not matter; what matters is that the compiler knows control never passes the call site.

Nothing? is Nothing made nullable. Since Nothing has zero instances, the only value that Nothing? can hold is null. That makes it the **type of null itself**:

val x = null       // inferred type: Nothing?
val y: Nothing? = null  // the only legal value

This is not just a curiosity -- it is how the type system stays consistent. When you write val x = null, the compiler needs to give x a type. There is no more specific type to pick (you did not say String? or Int?), so it infers Nothing?.

Nothing? is a subtype of every nullable type:

val a: String? = null   // Nothing? <: String?
val b: Int? = null      // Nothing? <: Int?
val c: Any? = null      // Nothing? <: Any?

In practice, a bare Nothing? variable is nearly useless because you can only ever assign null to it. You almost always want an explicit type annotation:

// Not useful:
val x = null  // Nothing? -- can only ever be null

// Useful:
val x: String? = null  // String? -- can hold a String later

Here is one of the most elegant uses of Nothing: **generic empty collections.**

emptyList() is declared as fun <T> emptyList(): List<T> -- the compiler infers T from whatever you assign it to. But under the hood, the implementation is a singleton object typed List<Nothing>:

// Kotlin stdlib (simplified)
internal object EmptyList : List<Nothing> { ... }
public fun <T> emptyList(): List<T> = EmptyList as List<T>

This works because Nothing is a subtype of every type, and List is covariant (out T), so List<Nothing> is assignable to any List<T>:

val names: List<String> = emptyList()  // T inferred as String
val numbers: List<Int> = emptyList()   // T inferred as Int

Covariance means that if A <: B, then List<A> <: List<B>. Since Nothing <: String, we get List<Nothing> <: List<String>.

The same principle applies to emptySet(), emptyMap(), and other empty collections. Without Nothing, the type system would need special-case rules. With Nothing, it falls out of normal subtyping -- no magic needed.

Now let us put it all together. Here is the full Kotlin type hierarchy in one picture:

           Any?           <- true top (supertype of everything)
          /    \
        Any    Nothing?   <- Nothing? = just null
       / | \      |
  String Int ... Nothing  <- bottom (subtype of everything)

- **Any?** is the supertype of every type, nullable or not. - **Any** is the root of all non-nullable types. - **Nothing** is the subtype of every type -- both nullable and non-nullable. - **Nothing?** is the subtype of every nullable type.

The hierarchy is **symmetric**: Any? at the top accepts everything, Nothing at the bottom fits into everything.

And where does Unit fit? Unit is an ordinary class that inherits from Any. It has exactly one instance (the Unit object). It is Kotlin's equivalent of Java's void, but it is a real type -- you can store it, pass it, and return it:

val u: Unit = Unit         // the singleton instance
val u2: Any = Unit         // Unit <: Any
fun log(msg: String): Unit = println(msg)  // returns Unit implicitly

Unit and Nothing sit at opposite ends of a different axis: Unit means "returns, but the value is meaningless" while Nothing means "never returns at all".

A common interview pattern is using Nothing to model **impossible states** in sealed hierarchies. If a generic sealed class has a type parameter that should not exist for a particular subtype, Nothing is the answer:

sealed class Either<out L, out R> {
    data class Left<out L>(val value: L) : Either<L, Nothing>()
    data class Right<out R>(val value: R) : Either<Nothing, R>()
}

Here, Left carries an L but has no R value. By setting R = Nothing, there is no possible value of type R that could exist -- which is exactly right, because Left does not carry one.

This pattern is type-safe without null:

val result: Either<String, Int> = Either.Left("error")
// Left<String> extends Either<String, Nothing>
// Nothing <: Int, so Either<String, Nothing> <: Either<String, Int>

You will see this same idea in Arrow's Either, Kotlin's own Result internals, and any time a generic type needs to express "this slot is unused".

Let us also clear up the Java interop side, since this comes up in interviews.

Kotlin's Any maps to java.lang.Object on the JVM, but they are not identical in Kotlin source. Any only exposes three members (equals, hashCode, toString). Java's Object extras like clone(), wait(), notify(), and getClass() are not on Any -- you need to cast to Object or use extension functions to access them:

val obj: Any = "hello"
// obj.clone()       // error: unresolved reference
// obj.getClass()    // error: unresolved reference
(obj as Object).javaClass  // works via cast
obj::class.java            // Kotlin way

Java's Void (capital V) is **not** Kotlin's Nothing. Void is a regular class that cannot be instantiated, but it is **not** the bottom type -- it does not subtype everything. Kotlin's Nothing has no Java equivalent. When Java interop is needed, Nothing simply erases to Void at the bytecode level.

And Java's void (lowercase) maps to Kotlin's Unit, not Nothing. A Java method returning void appears as returning Unit in Kotlin.

Finally, some practical guidelines for when you would actually **write** Nothing in your own code.

Most of the time, you never need to type Nothing explicitly -- the compiler infers it. But there are three situations where it appears in your signatures:

**1. Utility functions that always throw:**

fun unreachable(msg: String = "unreachable"): Nothing =
    throw AssertionError(msg)

**2. Generic types where a slot is intentionally empty:**

sealed class Outcome<out E, out T> {
    data class Failure<out E>(val error: E) : Outcome<E, Nothing>()
    data class Success<out T>(val value: T) : Outcome<Nothing, T>()
}

**3. Callbacks or interfaces that signal "this never completes":**

interface EventProcessor {
    fun processForever(): Nothing  // signals: this blocks indefinitely
}

Outside these patterns, if you find yourself reaching for Nothing, you probably want Unit instead. Remember: Unit = "returns, value not useful"; Nothing = "never returns". If your function finishes, its return type is not Nothing.

Back to Nothing & Any