skip to content

How do Unit and Nothing interact with type inference and generics — for example, the inferred type of `val x = null`, the result of `emptyList()`, and lambdas like `{ throw E() }`?

level: seniorimportance: nice to knowfreq 35%

answer

  1. null : Nothing? until context refines
  2. emptyList() : List<Nothing>, covariance shares it
  3. throw/TODO lambda : () -> Nothing fits () -> T
  4. Unit-coercion: last value discarded when Unit expected
  5. Pitfall: forEach in expression body infers Unit

basics

~20 s

Nothing is the type the compiler picks when an expression has no real value, like a lambda that only throws. null alone is Nothing?. The compiler then specializes these from context, and Unit is what value-less lambdas infer to.

solid answer

~40 s

`Nothing` is the inference default for value-less expressions. The literal `null` has type `Nothing?` until context refines it; `emptyList()` infers `List<Nothing>` (covariance makes `List<Nothing>` assignable to any `List<T>`); `throw`/`return`/`TODO()` are `Nothing`, so a lambda `{ throw E() }` infers `() -> Nothing`, assignable to `() -> T` for any T. Conversely, a lambda whose expected type returns `Unit` undergoes **Unit-coercion**: its last expression's value is discarded, so `forEach { it }` compiles even though `it` is not Unit. With generics, returning `Nothing` lets a single instance like `emptyList<Nothing>()` serve every element type, and functions like `fun <T> emptyList(): List<T>` plus `Nothing` as the lower bound make universal empties possible. Watch for **unintended Unit inference** in expression bodies: `fun f() = list.forEach { ... }` infers `Unit`, not the list.

code

kotlin · 6 lines
kotlin
val empties: List<Int> = emptyList()          // List<Nothing> <: List<Int>
val producer: () -> String = { TODO() }        // () -> Nothing <: () -> String
val onClick: () -> Unit = { computeAndReturnInt() }  // Int discarded (Unit-coercion)

// pitfall:
fun upper(xs: List<String>) = xs.map { it.uppercase() }   // List<String>, not Unit

go deeper

for a junior

May know null and empty collections work everywhere without explaining the type basis.

for a middle

Identifies Nothing? for null and Unit-coercion in lambdas.

for a senior

Explains List<Nothing> + covariance, () -> Nothing assignability, and the accidental-Unit pitfall.

for a principal

Reasons about how bottom/top types and variance combine to keep inference sound and to enable universal empties and value-less producers.

## Nothing as the inference 'no value' default When an expression yields no value, the compiler reaches for `Nothing`: ```kotlin val t = throw RuntimeException() // would be Nothing (illegal as statement-only, but illustrative) val lambda = { TODO() } // inferred () -> Nothing val f: (Int) -> String = { throw IllegalStateException() } // () -> Nothing fits () -> String ``` Because `Nothing` is a subtype of every type, `() -> Nothing` is a subtype of `() -> T` for any `T` (functions are covariant in their return type), so a throw-only lambda fits any functional type. ## null is Nothing? The bare literal `null` has type `Nothing?` — the only value of `Nothing?` is null. Context then refines it: ```kotlin val a = null // Nothing? val b: String? = null // refined to String? ``` ## emptyList() and List<Nothing> `emptyList<T>()` returns `List<T>`; an unconstrained `emptyList()` infers `List<Nothing>`. Since `List` is **covariant** (`out T`), `List<Nothing>` is a subtype of `List<Anything>`, so one shared empty instance works for every element type: ```kotlin val xs: List<String> = emptyList() // List<Nothing> assignable to List<String> ``` The same backs `emptySet()`, `emptyMap()`, and is why `listOf<Nothing>()` is the universal empty. ## Unit-coercion in lambdas When the **expected** type of a lambda returns `Unit`, the value of the last expression is ignored: ```kotlin val action: () -> Unit = { computeInt() } // Int discarded, returns Unit listOf(1,2,3).forEach { it * 2 } // result of it*2 ignored ``` This coercion only applies when Unit is *expected*; it does not happen for arbitrary return types. ## A common pitfall: accidental Unit Expression-body functions infer their type from the body's last expression. `forEach`, `also`, and many builders return Unit or the receiver: ```kotlin fun names(users: List<User>) = users.forEach { it.name } // inferred Unit, NOT List<String>! // fix: users.map { it.name } ``` ## Generics summary - `Nothing` as a type argument (`List<Nothing>`, `() -> Nothing`) makes value-less producers universally assignable thanks to covariance. - `Unit` as a type argument is a normal, non-special type (`Function0<Unit>`, `Channel<Unit>` for signals). ## Key terms - **Covariance (`out`)**: `Sub <: Super` implies `Container<Sub> <: Container<Super>`. - **Unit-coercion**: discarding a lambda's final value when Unit is expected. - **Bottom type**: Nothing, the inference fallback for value-less code.

  • Why can a single `emptyList<Nothing>()` instance be reused for every element type?
    List is covariant (`out T`), and Nothing is a subtype of every type, so List<Nothing> is a subtype of List<T> for all T.
  • Why might `fun f() = items.forEach { ... }` surprise you?
    forEach returns Unit, so the expression-body function infers Unit; use map (or a block body) if you intended to return a transformed value.

saying these in an interview costs you the question

  • Saying the type of `null` is Any? rather than Nothing?
  • Not knowing emptyList() infers List<Nothing> and why covariance matters
  • Claiming Unit-coercion applies to any expected return type
  • Missing that a throw-only lambda is () -> Nothing
  • Overlooking accidental Unit inference in expression-body functions

context