← Back to Kotlin Course | Chapter 7: Inheritance & Interfaces | Lesson 6 of 7

Sealed Classes

A sealed class gives you a fixed, known list of possible subtypes, so Kotlin can guarantee you've handled every single one of them.

Declaring a Sealed Class

sealed class restricts inheritance to a known, closed set of subclasses, which lets the compiler reason completely about every possible subtype at compile time.

Example: Declaring a Sealed Class

markup
sealed class Result
data class Success(val data: String) : Result()
data class Failure(val error: String) : Result()

fun main() {
    val result: Result = Success("Loaded")
    println(result)
}

Exhaustive when Without else

Because the compiler knows every subclass of a sealed class, a when expression covering all of them does not need an else branch and still compiles safely.

Example: Exhaustive when Without else

markup
sealed class Result
data class Success(val data: String) : Result()
data class Failure(val error: String) : Result()

fun describe(result: Result): String = when (result) {
    is Success -> "Success: ${result.data}"
    is Failure -> "Failure: ${result.error}"
}

fun main() {
    println(describe(Success("Data loaded")))
    println(describe(Failure("Timeout")))
}

Sealed Classes for Modeling States

Sealed classes are a great fit for representing a small, fixed set of states that each need to carry different data, such as loading states in an application.

Example: Sealed Classes for Modeling States

markup
sealed class LoadState
object Loading : LoadState()
data class Loaded(val items: Int) : LoadState()
data class Error(val message: String) : LoadState()

fun render(state: LoadState): String = when (state) {
    is Loading -> "Loading..."
    is Loaded -> "Loaded ${state.items} items"
    is Error -> "Error: ${state.message}"
}

fun main() {
    println(render(Loading))
    println(render(Loaded(10)))
}

Sealed Classes Catch Missed Cases

If a new subclass is later added to a sealed class, any exhaustive when expression elsewhere that handled every existing case will fail to compile until it handles the new one too, catching missed logic early.

Note: If you added a Triangle subclass here, this when would fail to compile until a Triangle branch was added.

Example: Sealed Classes Catch Missed Cases

markup
sealed class Shape
data class Circle(val radius: Double) : Shape()
data class Square(val side: Double) : Shape()

fun area(shape: Shape): Double = when (shape) {
    is Circle -> Math.PI * shape.radius * shape.radius
    is Square -> shape.side * shape.side
}

fun main() {
    println("Circle area: ${area(Circle(2.0))}")
    println("Square area: ${area(Square(3.0))}")
}
Common Mistakes
  1. Forgetting a sealed class's subclasses must all live in the same module (and, before certain Kotlin versions, the same file/package), causing a compile error if placed elsewhere.
  2. Adding an unnecessary else branch in a when over a sealed class, hiding the compiler's ability to warn you when a new subtype is added later.
  3. Confusing a sealed class with an enum; sealed subclasses can each carry their own different data, unlike a plain enum's fixed constant set.
Chapter Summary
  • sealed class restricts which classes can subclass it, and all direct subclasses must be known to the compiler at compile time.
  • A when expression over a sealed class's subtypes can be exhaustive without an else branch, since the compiler knows every possibility.
  • Sealed classes are ideal for representing a fixed set of related, but structurally different, states or results.
  • Adding a new subclass later causes any exhaustive when blocks to fail to compile until they handle the new case too.
🔒

Chapter Quiz — Complete all 7 topics to unlock

0/7 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.