Kotlin Academy · Lesson

Exhaustive when with Sealed Hierarchies

Write complete when expressions that cover every sealed variant.

Lesson 2 of 413 steps

Exhaustive when with Sealed Hierarchies is a free Kotlin Academy lesson on CoddyKit. This is lesson 2 of 4. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the Kotlin Academy learning path, and your progress syncs across the web and the CoddyKit app. The Kotlin Academy course includes 4 lessons in total.

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.

Free to start

Learn Kotlin with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
51
Lessons
203

Frequently Asked Questions

Is the “Exhaustive when with Sealed Hierarchies” lesson free?

Yes — any 3 lessons of the Kotlin Academy learning path, including “Exhaustive when with Sealed Hierarchies”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The Kotlin Academy course includes 4 lessons in total.

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, so you can start here or from the beginning and move at your own pace. This is lesson 2 of 4.

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

  1. sealed class vs sealed interface: When to Use Each
  2. Exhaustive when with Sealed Hierarchies
  3. Modeling UI State with Sealed Classes
  4. Nesting and Combining Sealed Hierarchies
← Back to Kotlin Academy