Sealed classes and exhaustive when

Kotlin language Mid-level Kotlin 2.0+ API 24+ Reviewed By Best Latest Apps

Predict the output

A teammate adds Empty to the sealed interface. What happens when you build?

sealed interface FeedState {
    data object Loading : FeedState
    data class Loaded(val items: List<String>) : FeedState
    data class Failed(val message: String) : FeedState
    data object Empty : FeedState
}

fun title(state: FeedState): String = when (state) {
    FeedState.Loading -> "Loading"
    is FeedState.Loaded -> "${state.items.size} items"
    is FeedState.Failed -> state.message
}
Options
Hint 1 of 1

Does the when cover every subtype?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A Compile error: the when must be exhaustive

Correct. The compiler knows every subtype and Empty isn't handled.

Why not the others?
  • B It compiles and returns an empty string for Empty. There's no default; a when expression must cover all cases.
  • C It compiles and throws at runtime for Empty. Kotlin catches this at compile time.
  • D It compiles because data objects are ignored. Every subtype counts.

30-second answer

A sealed class or interface has a fixed set of subtypes known at compile time, all in the same module and package. Because the compiler knows every case, a when expression over it must handle each one or have else. Adding a new case then breaks every when that forgot it, which is exactly what you want for UI state or results.

Practise saying it · 35 s. No recording, just a timer

Explain sealed types and exhaustive when.

Say your answer out loud, then check which of these points you covered:

  • Fixed set of subtypes
  • when must be exhaustive
  • New cases break forgotten branches
  • Avoid else
  • Models impossible states away

Deep dive

Use data object for cases without data, like Loading, and data classes for cases with data.

Sealed interfaces let one class belong to several hierarchies.

Avoid else in when over sealed types; it silences the check for future cases.

Enums suit fixed values without per-case data; sealed types suit cases with different data.

Common mistakes

  • Adding else to every when and losing exhaustiveness.
  • Using a Boolean pair like isLoading/isError that can both be true.
  • Thinking sealed subclasses can be declared in any module.

Senior take & follow-ups

Tie it to impossible states: a sealed UiState can't be loading and failed at once.

Follow-up questions

Think of your answer first, then open the hint.

  1. Sealed class vs enum?

    Show hint

    Enums have one instance per value; sealed cases can carry different data.

  2. Why avoid else?

    Show hint

    New cases wouldn't cause compile errors where they should be handled.

Compare on iOS: Enums with associated and raw values Swift enums with associated values and exhaustive switch play the same role.

Suggest a fix if something here is wrong or out of date.