← 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.
Syntax
kotlin
sealed class ClassName
class CaseA(val data: Type) : ClassName()
object CaseB : ClassName()

val result = when (value) {   // no else needed
    is CaseA -> value.data
    CaseB -> other
}

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
// Define a data class `Success`
data class Success(val data: String) : Result()
// Define a data class `Failure`
data class Failure(val error: String) : Result()

// Entry point: execution of the program starts here
fun main() {
    // Declare a read-only variable `result` (a `Result`), set to `Success("Loaded")`
    val result: Result = Success("Loaded")
    // Print `result` to the console, with a trailing newline
    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
// Define a data class `Success`
data class Success(val data: String) : Result()
// Define a data class `Failure`
data class Failure(val error: String) : Result()

// Define the function `describe` taking `result` and returning a String
fun describe(result: Result): String = when (result) {
    is Success -> "Success: ${result.data}"
    is Failure -> "Failure: ${result.error}"
}

// Entry point: execution of the program starts here
fun main() {
    // Print `describe(Success("Data loaded"))` to the console, with a trailing newline
    println(describe(Success("Data loaded")))
    // Print `describe(Failure("Timeout"))` to the console, with a trailing newline
    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
// Define the singleton object `Loading`
object Loading : LoadState()
// Define a data class `Loaded`
data class Loaded(val items: Int) : LoadState()
// Define a data class `Error`
data class Error(val message: String) : LoadState()

// Define the function `render` taking `state` and returning a String
fun render(state: LoadState): String = when (state) {
    is Loading -> "Loading..."
    is Loaded -> "Loaded ${state.items} items"
    is Error -> "Error: ${state.message}"
}

// Entry point: execution of the program starts here
fun main() {
    // Print `render(Loading)` to the console, with a trailing newline
    println(render(Loading))
    // Print `render(Loaded(10))` to the console, with a trailing newline
    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
// Define a data class `Circle`
data class Circle(val radius: Double) : Shape()
// Define a data class `Square`
data class Square(val side: Double) : Shape()

// Define the function `area` taking `shape` and returning a Double
fun area(shape: Shape): Double = when (shape) {
    is Circle -> Math.PI * shape.radius * shape.radius
    is Square -> shape.side * shape.side
}

// Entry point: execution of the program starts here
fun main() {
    // Print "Circle area: ${area(Circle(2.0))}" to the console, with a trailing newline
    println("Circle area: ${area(Circle(2.0))}")
    // Print "Square area: ${area(Square(3.0))}" to the console, with a trailing newline
    println("Square area: ${area(Square(3.0))}")
}
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
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.