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.
answer
- Expression produces a value -> must be exhaustive
- Statement = side effects only -> else optional
- Boolean/enum/sealed -> exhaustive without else
- Int/String/open -> needs else
- 1.7+: non-exhaustive statement on sealed/enum = warning
basics
~20 sIf 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 sExhaustiveness 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 linesfun describe(n: Int): String = when {
n < 0 -> "negative"
n == 0 -> "zero"
else -> "positive" // else required: Int is unbounded
}go deeper
Knows that a value-producing when needs an else or full coverage, and a side-effect when does not.
Articulates the expression-vs-statement distinction precisely and lists Boolean/enum/sealed as else-free cases.
Explains the 1.7+ statement warning/error and why exhaustiveness is a compile-time refactoring safety net.
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'