Represent finite states without invalid combinations
A sealed hierarchy lets each state carry only the data that makes sense for it. This avoids combinations such as loading=true and error=true in a bag of independent flags.
sealed interface CourseState {
data object Loading : CourseState
data class Ready(val titles: List<String>) : CourseState
data class Failed(val message: String) : CourseState
}
fun label(state: CourseState): String = when (state) {
CourseState.Loading -> "Loading"
is CourseState.Ready -> "${state.titles.size} lessons"
is CourseState.Failed -> state.message
}
The compiler checks that the expression handles every subtype. Adding an else branch would hide future missing cases, so omit it when exhaustive handling is intended.
A hierarchy describes possible values, not legal transitions by itself. You still need application logic deciding whether loading can follow failure or whether stale content remains visible during refresh.
Exercise
Add an Empty state and update the renderer. Write a transition function for retrying only from Failed.
Check: explain why representing “refreshing with existing content” may require a different state model than initial loading.