skip to content

What is the difference between `when` used as a statement vs an expression over a sealed type with respect to exhaustiveness, and how did Kotlin 1.7 change this?

level: middleimportance: should knowfreq 45%

answer

  1. Expression = value used = always must be exhaustive
  2. Statement = value ignored
  3. Pre-1.7 statement not checked → silent gaps
  4. 1.7 added statement exhaustiveness (warn→error)
  5. .exhaustive hack now obsolete

basics

~20 s

As an expression (its value is used), when over a sealed type must cover all subtypes. As a statement (value ignored), older Kotlin didn't check this; since 1.7 it does and warns/errors if a case is missing.

solid answer

~40 s

Exhaustiveness is enforced when a `when` is used as an **expression** — its result is assigned, returned, or passed as an argument — and the subject has a closed set of values (sealed type, enum, or `Boolean`). Then every case must be covered or it won't compile. A `when` **statement** ignores the result. Before Kotlin 1.7, statement `when`s over sealed types were *not* checked, so adding a subtype could silently skip handling. Kotlin 1.7 made non-exhaustive `when` statements over sealed/enum/Boolean subjects a **warning, progressing to an error** in later language versions, restoring the safety net. The old `.exhaustive` extension hack (assigning the `when` to force expression context) is therefore obsolete. Each covered branch still smart-casts.

go deeper

for a junior

Knows expression when must cover all cases; may not know statement nuances.

for a middle

Explains expression-vs-statement and that 1.7 added statement exhaustiveness, deprecating the .exhaustive trick.

for a senior

Discusses warning-to-error progression by language version and why catch-all else defeats the purpose.

for a principal

Frames exhaustiveness as totality enforcement and weighs migration/language-version policy across a large codebase.

## Definitions - **Expression context**: the `when`'s value is consumed — `val x = when(...)`, `return when(...)`, `f(when(...))`. - **Statement context**: the `when` stands alone and its value is discarded. - **Exhaustive**: every possible value of the subject is matched (all sealed subtypes, all enum entries, both Booleans, plus `null` if nullable). ## Expression: always must be exhaustive A `when` expression must be exhaustive because it has to produce a value for *every* input; otherwise its result type/behavior would be undefined. Over a sealed type you achieve this by listing all subtypes (no `else` needed). ```kotlin sealed interface Cmd object Start : Cmd object Stop : Cmd val label: String = when (cmd) { // expression -> must be exhaustive Start -> "go" Stop -> "halt" } ``` ## Statement: the historical gap Before Kotlin 1.7 a statement `when` like this compiled even if incomplete: ```kotlin when (cmd) { // statement (pre-1.7): NOT checked Start -> println("go") // Stop missing -> silently does nothing } ``` People worked around it with a hack: ```kotlin val <T> T.exhaustive get() = this when (cmd) { ... }.exhaustive // forces expression context ``` ## What Kotlin 1.7 changed Kotlin 1.7 introduced **exhaustiveness checks for `when` *statements*** whose subject is a sealed class/interface, enum, or Boolean. A missing branch now produces a **warning** (and an **error** under newer language-version settings / future releases). This means: - You get the same "add a branch" safety even when ignoring the result. - The `.exhaustive` trick is no longer needed and is deprecated in practice. ## Practical guidance - Prefer modeling outcomes so the `when` is naturally an expression (return its value) — clearest and always checked. - Don't add a catch-all `else` to silence the statement warning; add the real branches so future subtypes still break the build. - Remember the check also covers enums and Boolean, not just sealed types. ## Edge: nullable subject If the subject is nullable, a `null ->` branch (or `?` handling) is required for exhaustiveness in both contexts.

  • Why is adding an `else` a poor way to silence a non-exhaustive `when` statement warning?
    `else` swallows any future subtype at runtime, removing the compile-time signal that you must handle the new case — the very protection sealed types give you.
  • Does the 1.7 statement check apply only to sealed types?
    No — it also applies to enums and Boolean subjects, any subject with a known closed value set.

saying these in an interview costs you the question

  • Claims statement and expression are always identical
  • Still recommends the `.exhaustive` extension hack as best practice
  • Thinks only enums (not sealed) get statement checks
  • Adds blanket `else` to silence the warning
  • Unaware of the 1.7 change

context