Modeling UI Navigation with Kotlin Sealed Classes: Exhaustive When Expressions
See how Kotlin sealed classes let you model UI navigation states as a closed hierarchy, get compile‑time exhaustive when checks, and avoid impossible states.
21 Jan 2026, 00:29 UTC

Why Boolean Flags Lead to Impossible UI States
Many apps track UI with separate flags like isLoading, hasError, and currentItem. Setting them independently can produce combinations that never make sense—e.g., showing a spinner while also displaying an error.
Sealed Classes as a Closed State Hierarchy
A sealed class limits its subclasses to the same file, giving the compiler full knowledge of all possible states. When used in a when expression, Kotlin checks exhaustiveness, so omitting a branch results in a compile‑time error.
Worked Example: Navigation State for a Profile Screen
sealed class ProfileNavState {
object Home : ProfileNavState()
data class Detail(val userId: String) : ProfileNavState()
data class Error(val message: String) : ProfileNavState()
}
fun describe(state: ProfileNavState): String =
when (state) {
is ProfileNavState.Home -> "Showing list of profiles"
is ProfileNavState.Detail -> "Displaying profile ${state.userId}"
is ProfileNavState.Error -> "Error: ${state.message}"
// No else needed; compiler would complain if a new subclass is added
}
Verification Steps
- Place the sealed class and function in a Kotlin file.
- Comment out the
is ProfileNavState.Errorbranch. - Recompile; you should see an error like “when expression must be exhaustive”.
- Restore the branch and the code compiles cleanly.
Trade‑offs and Limitations
- File‑local scope: All subclasses must be declared in the same file. In a multi‑module project you cannot add a new state from another module without editing the sealed class file.
- Serialization: Libraries like Gson need extra configuration to discriminate subclasses;
kotlinx.serializationhandles this automatically with a@SerialNameor a type discriminator. - Large hierarchies: When a sealed class grows beyond ~15 subclasses, the
whenblock can become verbose. Consider nesting sealed classes or switching to a sealed interface (Kotlin 1.9+) for better modularity.
Practical Next Step
Locate a UI component that currently uses a set of boolean flags or a nullable data holder to represent its state. Replace those variables with a sealed class that enumerates every possible state, then rewrite the UI‑rendering logic as an exhaustive when. Run the verification steps above to confirm the compiler now guards against missing cases.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.