sealed class / sealed interface
A sealed class in Kotlin restricts subclasses to the same file (or nested inside the class). Combined with when expressions, the compiler enforces exhaustive checks, making sealed classes ideal for state management and result types.
Syntax
// Define a sealed class
sealed class Result<out T>
data class Success<T>(val data: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
object Loading : Result<Nothing>()
// Handle all cases exhaustively with when (no else needed)
fun handle(result: Result<String>) = when (result) {
is Success -> println("Success: ${result.data}")
is Error -> println("Error: ${result.message}")
is Loading -> println("Loading...")
}
// sealed interface (Kotlin 1.5+)
sealed interface Shape
data class Circle(val radius: Double) : Shape
data class Rectangle(val w: Double, val h: Double) : Shape
Syntax Overview
| Syntax | Description |
|---|---|
| sealed class Name | Defines a class whose subclasses can only exist within the same compilation unit. |
| sealed interface Name | Defines a sealed interface, available in Kotlin 1.5 and later. |
| is SubclassName | Type check used in a when expression. With a sealed class, all cases are covered and else is not required. |
| object SubclassName : Parent() | Defines a stateless subclass as a singleton. |
Sample Code
// UI state management pattern
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String, val code: Int = 0) : UiState<Nothing>()
}
// Simulate a network operation
fun fetchUser(id: Int): UiState<String> = when (id) {
0 -> UiState.Loading
in 1..100 -> UiState.Success("User-$id")
else -> UiState.Error("User not found", 404)
}
// Handle all cases exhaustively with when (compiler verifies coverage)
fun render(state: UiState<String>): String = when (state) {
is UiState.Loading -> "Loading..."
is UiState.Success -> "Data: ${state.data}"
is UiState.Error -> "Error(${state.code}): ${state.message}"
}
// Command pattern using sealed class
sealed class Command
data class Move(val dx: Int, val dy: Int) : Command()
data class Rotate(val degrees: Int) : Command()
object Stop : Command()
fun execute(cmd: Command) {
when (cmd) {
is Move -> println("Move: (${cmd.dx}, ${cmd.dy})")
is Rotate -> println("Rotate: ${cmd.degrees} degrees")
is Stop -> println("Stop")
}
}
fun main() {
// UiState demo
listOf(0, 1, 999).forEach { id ->
val state = fetchUser(id)
println(render(state))
}
println()
// Command pattern demo
val commands = listOf(Move(10, 5), Rotate(90), Move(-3, 2), Stop)
commands.forEach { execute(it) }
}
Notes
Because sealed class restricts the possible states at the type level, it is well suited for implementing state machines such as Success/Error/Loading, result types, and algebraic data types (ADTs).
When all subclasses are handled in a when expression, the compiler verifies exhaustiveness. If you add a new subclass and forget to handle it, you get a compile error, which improves maintainability. (Exhaustive checking is active when when is used as an expression.)
For class basics, see class / constructor. For smart casts, see Smart cast / as?.
If you find any errors or copyright issues, please contact us.