skip to content

How does `when` behave as an expression, and what exhaustiveness rules apply to it?

level: middleimportance: must knowfreq 65%

answer

  1. when returns the matched branch's value
  2. expression use => must be exhaustive
  3. else OR all enum entries OR all sealed subtypes
  4. sealed without else => new subtype is a compile error
  5. is enables smart-cast inside the branch

basics

~20 s

when can return a value, so you can assign its result. When used as an expression it must cover every possible case — usually by adding an else branch, or covering all options of an enum or sealed type.

solid answer

~50 s

`when` is an expression: the branch whose condition matches yields the value, and that value can be assigned, returned, or passed. As an expression, `when` must be **exhaustive** — every possible input must be handled — otherwise it won't compile. You satisfy exhaustiveness with an `else` branch, or by covering all members of an `enum class` or all subtypes of a `sealed class`/`sealed interface` (then no `else` is needed). Covering a sealed hierarchy without `else` is preferred because adding a new subtype turns the missing case into a **compile error**, not a silent fallthrough. Each branch value is the last expression of its block. `when` with a subject (`when (x) { ... }`) matches values/ranges/types (`is`, `in`); subjectless `when { cond -> ... }` acts like an if/else-if chain. Note: a `when` used purely as a statement is **not** required to be exhaustive (though the compiler now warns on non-exhaustive sealed/enum statements).

code

kotlin · 8 lines
kotlin
sealed interface Shape
data class Circle(val r: Double) : Shape
data class Square(val s: Double) : Shape

fun area(shape: Shape): Double = when (shape) {  // exhaustive, no else needed
    is Circle -> Math.PI * shape.r * shape.r      // smart-cast to Circle
    is Square -> shape.s * shape.s
}

go deeper

for a junior

Knows when can return a value and that an else branch is often used.

for a middle

Explains exhaustiveness in expression position and the three ways to satisfy it (else / all enums / all sealed subtypes).

for a senior

Argues for sealed coverage without else for compile-time safety, knows smart-cast in is branches and statement-vs-expression exhaustiveness nuance.

for a principal

Designs domain models around sealed hierarchies + exhaustive when to make illegal states unrepresentable and guarantee maintenance breaks loudly.

## `when` as an expression Like `if`, `when` yields a value — the value of the matched branch: ```kotlin val sign = when { n > 0 -> "positive" n < 0 -> "negative" else -> "zero" } ``` The result type is the common supertype of all branch values. ## Exhaustiveness (expression position) When the result is **used**, `when` must be **exhaustive** — the compiler must see that some branch always matches. Ways to satisfy it: - An **`else`** branch (catch-all). - Covering **all enum entries**: ```kotlin enum class Dir { N, S, E, W } val dx = when (d) { Dir.N -> 0; Dir.S -> 0; Dir.E -> 1; Dir.W -> -1 } // exhaustive, no else ``` - Covering **all subtypes of a sealed class/interface**: ```kotlin sealed interface Result data class Ok(val v: Int) : Result data object Err : Result val msg = when (r) { is Ok -> "ok ${r.v}" Err -> "failed" } // exhaustive without else ``` ## Why prefer sealed/enum over `else` If you add `Loading : Result` later, an `else`-based `when` silently keeps working (possibly wrongly), while the exhaustive sealed `when` becomes a **compile error**, forcing you to handle the new case. This is a major reason sealed hierarchies pair so well with `when`. ## Subject vs subjectless - `when (x) { 1 -> ...; in 2..9 -> ...; is String -> ...; else -> ... }` matches against `x` using equality, `in` (ranges/collections), and `is` (type checks; **smart-cast** applies inside the branch). - Subjectless `when { boolCond -> ... }` is an if/else-if ladder. ## Statement position A `when` used only for side effects is not forced to be exhaustive (the compiler warns for non-exhaustive enum/sealed statements but does not fail). ## Key APIs/keywords `when`, `else`, `is` (smart cast), `in` (range/collection), `sealed class`/`sealed interface`, `enum class`.

  • Why is a sealed `when` without `else` safer than one with `else`?
    Adding a new subtype breaks compilation of the exhaustive when, forcing you to handle it; an `else` would silently absorb the new case.
  • Does a `when` used as a statement have to be exhaustive?
    No — it compiles, though the compiler emits a warning for non-exhaustive enum/sealed statements. Only expression-position when is required to be exhaustive.

saying these in an interview costs you the question

  • Saying `when` always needs an `else`
  • Not knowing sealed/enum coverage makes else unnecessary
  • Claiming exhaustiveness is required even in statement position (as a hard error)
  • Forgetting smart-cast works in `is` branches
  • Thinking subjectless `when` can't replace if/else-if

context