skip to content

when Exhaustiveness

A when used as an expression must cover every case, and with an enum or sealed subject the compiler can prove that without an else branch. Interviewers like this because dropping the else is what turns 'someone added a subclass' from a runtime surprise into a compile error.

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

questions

5

In Kotlin, when must a `when` be exhaustive (cover all cases), and when can it leave cases out? Explain the difference between a `when` expression and a `when` statement.

level: juniorimportance: must knowfreq 70%

answer

  1. Expression produces a value -> must be exhaustive
  2. Statement = side effects only -> else optional
  3. Boolean/enum/sealed -> exhaustive without else
  4. Int/String/open -> needs else
  5. 1.7+: non-exhaustive statement on sealed/enum = warning

basics

~20 s

If you use when to produce a value (like assigning it to a variable or returning it), it must handle every possible case. If you use when just to do an action and ignore its value, it does not have to cover everything.

solid answer

~50 s

Exhaustiveness depends on whether the `when` is used as an expression or a statement. A `when` expression yields a value (assigned to a variable, returned from a function, or used as the last expression of a lambda/body), and the compiler requires it to cover every possible value of the subject, usually via an `else` branch. A `when` statement is used only for its side effects and its result is discarded, so it need not be exhaustive and an `else` is optional. The compiler can prove exhaustiveness without `else` when the subject is a `Boolean`, an `enum class`, or a `sealed class`/`sealed interface`, because the full set of cases is known at compile time. Since Kotlin 1.7+, a non-exhaustive `when` statement on a sealed/enum subject is reported as a warning (and an error in newer language versions) to push you toward handling all cases.

code

kotlin · 5 lines
kotlin
fun describe(n: Int): String = when {
    n < 0 -> "negative"
    n == 0 -> "zero"
    else -> "positive" // else required: Int is unbounded
}

go deeper

for a junior

Knows that a value-producing when needs an else or full coverage, and a side-effect when does not.

for a middle

Articulates the expression-vs-statement distinction precisely and lists Boolean/enum/sealed as else-free cases.

for a senior

Explains the 1.7+ statement warning/error and why exhaustiveness is a compile-time refactoring safety net.

for a principal

Frames exhaustiveness as part of API/domain modeling: choosing sealed hierarchies so the compiler enforces total handling across the codebase.

## What "exhaustive" means A `when` is **exhaustive** when its branches cover every possible value the subject can take, so control flow can never "fall through" with no matching branch. ## Expression vs statement — the deciding factor - A **`when` expression** *produces a value*: it is on the right of `=`, after `return`, used as a function's single-expression body, or is the last expression of a block. Because a value must always be produced, **the compiler requires exhaustiveness**. - A **`when` statement** is used *only for side effects* (each branch does something; the result is thrown away). It is **not required** to be exhaustive; an `else` is optional. ```kotlin enum class Direction { NORTH, SOUTH } // Expression -> MUST be exhaustive fun label(d: Direction): String = when (d) { Direction.NORTH -> "up" Direction.SOUTH -> "down" // no else needed: enum cases are all covered } // Statement -> exhaustiveness not required fun log(d: Direction) { when (d) { Direction.NORTH -> println("up") // SOUTH omitted: allowed for a statement (but warned for enum/sealed) } } ``` ## When the compiler can prove exhaustiveness without `else` The compiler knows the complete set of values for: - **`Boolean`** — only `true`/`false`. - **`enum class`** — the finite list of entries. - **`sealed class` / `sealed interface`** — all direct subtypes are known in the same module/package. For these, listing every case makes the `when` exhaustive **without** an `else`. For open types (e.g. `Int`, `String`, an open class), the set is unbounded, so you need an `else`. ## The modern warning/error Since Kotlin **1.7**, a **statement** `when` over a sealed/enum subject that does *not* cover all cases produces a compiler **warning** ("Non-exhaustive 'when' statements ... will be prohibited"); with newer language-version settings it becomes an **error**. This nudges statement `when`s to be as exhaustive as expression `when`s, so adding a new enum entry or sealed subtype surfaces every place that must be updated. ## Why this matters Exhaustiveness on sealed/enum subjects is a **compile-time safety net**: add a subtype, and every exhaustive `when` that forgot it fails to compile — far safer than a silent runtime fall-through.

  • Why does a `when` expression force exhaustiveness but a statement does not?
    An expression must always yield a value, so every input needs a branch; a statement discards its result, so an unmatched subject can legitimately do nothing.
  • How do you turn a non-exhaustive `when` statement into one the compiler checks?
    Use it as an expression (e.g. assign it to `val x = when(...)` or return it), or rely on the 1.7+ sealed/enum warning that flags missing cases.

An expression is like a vending machine that must always hand back an item for any button; a statement is like flipping switches where some switches can simply do nothing.

saying these in an interview costs you the question

  • Claiming every `when` always needs an `else`
  • Saying a `when` statement must be exhaustive
  • Thinking exhaustiveness is a runtime check rather than compile-time
  • Not knowing Boolean/enum/sealed allow omitting `else`
  • Confusing 'exhaustive' with 'has an else branch'

context

open as a page

You have a `when` expression over a `sealed interface`. Should you add an `else` branch? What are the trade-offs of including vs omitting it?

level: middleimportance: must knowfreq 65%

basics

~20 s

Usually omit the else. Then if someone adds a new subtype later, the code stops compiling and points you to every when you must update. Adding an else hides those spots and lets bugs slip through.

open as a page

You want a side-effect-only `when` over an enum to be checked for exhaustiveness so adding an enum entry breaks the build. How do you make a statement `when` exhaustive in Kotlin?

level: middleimportance: should knowfreq 45%

basics

~20 s

Make the when produce a value so it counts as an expression. The simplest way is to put it after a tiny expression like assigning its result, so the compiler insists every case is handled. In modern Kotlin you also just get a warning automatically.

open as a page

Explain how the compiler decides a `when` over a sealed type is exhaustive, including the role of subtype visibility/module boundaries and nullability of the subject.

level: seniorimportance: should knowfreq 35%

basics

~20 s

The compiler can only check a when if it knows every subtype. Sealed types declare all their subtypes up front, so it can. If the value can be null, you must also handle the null case, or the when is not complete.

open as a page

A teammate argues every sealed `when` should have a defensive `else -> error("unreachable")` 'just in case'. Critique this from a maintainability and correctness standpoint, and describe when it is actually justified.

level: principalimportance: should knowfreq 25%

basics

~20 s

Adding else -> error(...) to a sealed when usually hurts. It silences the compiler check, so when someone adds a new case the code still compiles and only fails at runtime. Without else, the build breaks and points you to every place to fix.

open as a page