Exhaustive when with Sealed Hierarchies
Write complete when expressions that cover every sealed variant.
Exhaustive when with Sealed Hierarchies is a free Kotlin Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Kotlin Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Exhaustive when
A when expression used as an expression must be exhaustive — every possible value handled. Sealed hierarchies let the compiler verify this for you.
Non-Exhaustive when on Open Type
For ordinary classes, the compiler can't check all subtypes — it requires an else branch.
open class Shape
class Circle : Shape()
class Square : Shape()
fun describe(s: Shape) = when (s) {
is Circle -> "circle"
is Square -> "square"
else -> "unknown" // required
}
fun main() { println(describe(Circle())) }Exhaustive when on Sealed
For a sealed class, the compiler knows all subtypes. No else needed if every case is covered.
sealed class Result
class Success(val value: Int) : Result()
class Failure(val msg: String) : Result()
fun describe(r: Result) = when (r) {
is Success -> "ok: ${r.value}"
is Failure -> "err: ${r.msg}"
}
fun main() {
println(describe(Success(42)))
println(describe(Failure("nope")))
}Sealed Interface
Sealed interfaces give you exhaustive checking even when classes need multiple inheritance.
sealed interface Animal
class Dog : Animal
class Cat : Animal
fun speak(a: Animal) = when (a) {
is Dog -> "woof"
is Cat -> "meow"
}
fun main() {
println(speak(Dog()))
println(speak(Cat()))
}Smart Casts in Branches
Inside each is branch, Kotlin smart-casts the variable to the specific type — no manual casting required.
sealed class Event
data class Click(val x: Int, val y: Int) : Event()
data class Key(val char: Char) : Event()
fun handle(e: Event) = when (e) {
is Click -> "clicked at (${e.x},${e.y})" // smart-cast
is Key -> "key=${e.char}"
}
fun main() {
println(handle(Click(10, 20)))
println(handle(Key('A')))
}Forgetting a Case
If you add a new variant later, every exhaustive when still using the sealed type as expression fails to compile — a great safety net.
sealed class Status
object Idle : Status()
object Running : Status()
// If we add object Done : Status(), every exhaustive when must update.
fun text(s: Status): String = when (s) {
is Idle -> "idle"
is Running -> "running"
}
fun main() { println(text(Idle)) }Sealed with Data Classes
Common pattern: data classes as variants of a sealed hierarchy.
sealed class UiState {
object Loading : UiState()
data class Success(val data: List<String>) : UiState()
data class Error(val message: String) : UiState()
}
fun render(state: UiState) = when (state) {
UiState.Loading -> "loading..."
is UiState.Success -> "got ${state.data.size} items"
is UiState.Error -> "error: ${state.message}"
}
fun main() {
println(render(UiState.Loading))
println(render(UiState.Success(listOf("a", "b"))))
println(render(UiState.Error("404")))
}Using when as Statement
When used as a statement (not expression), when doesn't need to be exhaustive. But you give up the safety net.
sealed class Cmd
object Start : Cmd()
object Stop : Cmd()
fun execute(c: Cmd) {
// Statement form: no error if incomplete (avoid this style)
when (c) {
Start -> println("started")
Stop -> println("stopped")
}
}
fun main() { execute(Start); execute(Stop) }Forcing Exhaustive Check on Statement
Assign the when to a value (or use the .exhaustive trick) so the compiler enforces all cases.
sealed class Cmd
object Start : Cmd()
object Stop : Cmd()
fun execute(c: Cmd) {
val handled: Unit = when (c) { // expression context now
Start -> println("started")
Stop -> println("stopped")
}
}
fun main() { execute(Start) }Multi-Case Branches
Group cases with commas to share a body across multiple variants.
sealed class Event
class Click : Event()
class Tap : Event()
class Drag : Event()
fun isPointer(e: Event) = when (e) {
is Click, is Tap, is Drag -> true
}
fun main() { println(isPointer(Tap())) }Nested Sealed Types
Sealed hierarchies can nest — the exhaustive check still works on deep trees.
sealed class Network {
sealed class Wifi : Network() {
object Open : Wifi()
data class Secured(val ssid: String) : Wifi()
}
object Cellular : Network()
}
fun describe(n: Network) = when (n) {
Network.Wifi.Open -> "open wifi"
is Network.Wifi.Secured -> "secured: ${n.ssid}"
Network.Cellular -> "cellular"
}
fun main() {
println(describe(Network.Wifi.Secured("home")))
println(describe(Network.Cellular))
}Quick Check
What's the main benefit of using when with sealed hierarchies?
Recap
Pair sealed classes/interfaces with when for compile-time exhaustiveness. Smart casts simplify branch bodies. Use as an expression (or assign result) to force the check; group cases with commas; nest sealed types for tree-shaped state.
Frequently asked questions
Is the “Exhaustive when with Sealed Hierarchies” lesson free?
Yes — the full text of “Exhaustive when with Sealed Hierarchies” is free to read here on the web, and the Kotlin Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Kotlin Academy course, upgrade to CoddyKit PRO.
What will I learn in “Exhaustive when with Sealed Hierarchies”?
Write complete when expressions that cover every sealed variant. You practise Kotlin Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Kotlin Academy?
No prior experience is required. Kotlin Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Exhaustive when with Sealed Hierarchies” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Kotlin Academy lesson?
Yes. Every Kotlin Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- sealed class vs sealed interface: When to Use Each
- Exhaustive when with Sealed Hierarchies
- Modeling UI State with Sealed Classes
- Nesting and Combining Sealed Hierarchies