Lesson 9 / 25

Sealed Types and Enums

Closed hierarchies with exhaustive when.

Model every possible state

An enum class defines a fixed set of constants, which can have properties and methods; entries lists them (in recent versions, replacing values()). A sealed class or sealed interface restricts its direct subtypes to the same module and package, so the compiler knows every subtype. Subtypes can be data classes carrying different data, or data objects for singletons. Because the set is closed, a when over a sealed type is exhaustive without else, and adding a new subtype makes the compiler flag every when that must handle it. This is ideal for UI state, results and commands.

Loading state with a sealed interface

Exhaustive when, no else branch.

sealed interface LoadState {
    data object Loading : LoadState
    data class Success(val items: List<String>) : LoadState
    data class Error(val message: String, val retryable: Boolean) : LoadState
}

enum class Priority(val weight: Int) { LOW(1), MEDIUM(5), HIGH(10) }

fun render(state: LoadState): String = when (state) {
    LoadState.Loading -> "Spinner"
    is LoadState.Success -> "Showing ${state.items.size} items"     // smart cast
    is LoadState.Error -> if (state.retryable) "Retry: ${state.message}" else state.message
}

fun maxWeight(): Int = Priority.entries.maxOf { it.weight }

Avoid else on sealed when

Adding else to a when over a sealed type silences the compiler when a new subtype appears. Listing every case keeps the exhaustiveness check working for you.

Quick check: Why can `when` over a sealed interface omit `else`?

  • when never needs else
  • Sealed types cannot be used in when
  • The compiler knows all direct subtypes, so it can check exhaustiveness
  • Sealed types are always enums
Answer

The compiler knows all direct subtypes, so it can check exhaustiveness — Sealed hierarchies are closed.