skip to content

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%

answer

  1. Make statement into expression -> use its value
  2. .exhaustive extension: val <T> T.exhaustive get() = this
  3. Or assign: val x = when(...)
  4. 1.7+ warns on non-exhaustive sealed/enum statement
  5. allWarningsAsErrors makes it break the build

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.

solid answer

~50 s

A statement `when` isn't required to be exhaustive, so historically people forced the check by making it an **expression**. The classic idiom is the `.exhaustive` trick: an extension `val <T> T.exhaustive get() = this` appended as `when (e) { ... }.exhaustive`, which uses the result and thus demands full coverage. Another way is to assign the result: `val unused = when (e) { ... }`. Since Kotlin **1.6/1.7**, this is largely obsolete: the compiler emits a **warning** for non-exhaustive `when` **statements** over sealed/enum subjects ("Non-exhaustive 'when' statements ... will be prohibited"), and with a sufficiently new language version it becomes an **error**. So in current Kotlin you simply omit `else`, cover all enum entries, and let the built-in check fail or warn when an entry is added. The `.exhaustive` extension is now a legacy pattern you may still see in older codebases.

code

kotlin · 11 lines
kotlin
val <T> T.exhaustive: T get() = this

enum class Color { RED, GREEN, BLUE }

fun handle(c: Color) {
    when (c) {
        Color.RED   -> println("r")
        Color.GREEN -> println("g")
        Color.BLUE  -> println("b")
    }.exhaustive // legacy way to force compile-time coverage
}

go deeper

for a junior

Recognizes that turning the when into an expression (e.g. assigning its result) forces coverage.

for a middle

Knows both the .exhaustive idiom and the modern 1.7+ compiler warning, and why each works.

for a senior

Explains promoting the warning to an error (allWarningsAsErrors / language-version) and why else would defeat the goal.

for a principal

Sets team policy: treat non-exhaustive sealed/enum when as build-breaking, retire legacy .exhaustive, prefer sealed modeling so coverage is compiler-enforced.

## The problem A **statement `when`** (used only for side effects) is *not* required to be exhaustive. So this compiles even though `BLUE` is unhandled: ```kotlin enum class Color { RED, GREEN, BLUE } fun handle(c: Color) { when (c) { // statement: result discarded Color.RED -> doRed() Color.GREEN -> doGreen() // BLUE missing -> historically silent } } ``` You want adding a new entry to **break the build**. ## Idiom 1 (legacy): the `.exhaustive` trick Force the `when` to be an **expression** by *using its value*: ```kotlin val <T> T.exhaustive: T get() = this when (c) { Color.RED -> doRed() Color.GREEN -> doGreen() Color.BLUE -> doBlue() }.exhaustive // value is read -> must be exhaustive ``` Reading `.exhaustive` makes the `when` an expression, so the compiler requires every case. Equivalent: `val x = when (c) { ... }` (assign the result). ## Idiom 2 (modern, preferred): rely on the compiler Since Kotlin **1.6** (opt-in) / **1.7** (default warning), a non-exhaustive **statement** `when` over a **sealed** or **enum** subject produces a compiler warning, and newer `-language-version` settings promote it to an **error**. So the modern approach is simply: ```kotlin fun handle(c: Color) { when (c) { Color.RED -> doRed() Color.GREEN -> doGreen() Color.BLUE -> doBlue() } // no else; omitting a case now warns/errors } ``` You can elevate warnings to errors project-wide (`allWarningsAsErrors`) to guarantee the build breaks. ## Key facts - The `.exhaustive` pattern works because exhaustiveness is tied to **expression usage**, not to any special keyword. - It only buys you anything for **sealed/enum/Boolean** subjects, where the compiler knows the full case set; for `Int`/`String` you'd still need `else`. - Prefer the built-in 1.7+ check over the legacy extension in new code. ## Summary Force a value to be used (assign it or `.exhaustive`) on older compilers; on modern Kotlin, just omit `else` over a sealed/enum subject and treat the warning as an error.

  • Why does the `.exhaustive` extension actually enforce exhaustiveness?
    Reading the `.exhaustive` property uses the `when`'s value, turning it into an expression; expressions must always produce a value, so the compiler requires every case to be covered.
  • Is the `.exhaustive` trick still needed today?
    Largely not. Since Kotlin 1.7 a non-exhaustive statement `when` over sealed/enum subjects warns by default (error in newer language versions), so you can rely on the compiler and treat warnings as errors.

saying these in an interview costs you the question

  • Claiming you cannot make a statement `when` exhaustive at all
  • Adding `else` (which defeats the purpose of catching new cases)
  • Not knowing modern Kotlin warns automatically
  • Thinking `.exhaustive` is a built-in keyword rather than an extension
  • Believing it works for Int/String subjects without else

context