Modern examples of 3 practical examples of Kotlin sealed classes in real apps

If you’re hunting for clear, modern examples of 3 practical examples of Kotlin sealed classes, you’re in the right place. Too many tutorials stay abstract; here we’ll stay close to real Android and backend code you’d actually ship in 2024 and 2025. Sealed classes are about modeling **closed sets of states** in a way the compiler can fully understand. That means safer `when` expressions, fewer illegal states, and code that’s easier to refactor as your app grows. In this guide, we’ll walk through several real examples of how sealed classes can model network results, UI state, navigation, analytics events, and more. Along the way, we’ll compare sealed classes to enums and interfaces, and highlight when each tool fits best. By the end, you’ll not only see examples of 3 practical examples of Kotlin sealed classes, you’ll have 6–8 concrete patterns you can lift into your own projects—whether you’re building Android apps with Kotlin, Ktor services, or multiplatform shared code.
Written by
Jamie
Published

Real-world examples of 3 practical examples of Kotlin sealed classes

Instead of starting with theory, let’s jump straight into real examples. When people ask for examples of 3 practical examples of Kotlin sealed classes, they usually mean: “Show me the patterns I’ll actually use in production.”

So we’ll center the discussion around three big buckets you hit constantly in Kotlin projects:

  • Representing network or data loading results
  • Modeling UI state and navigation
  • Structuring domain events and errors

Inside each bucket, we’ll walk through multiple real examples, so you get more than just three toy snippets.


Example of sealed classes for network and data results

One of the best examples of 3 practical examples of Kotlin sealed classes is the classic Result pattern. You’ve probably seen a version of this in Android apps using Retrofit, Ktor, or GraphQL.

1. API result wrapper for Retrofit / Ktor

You want every network call to return either a success with data or some flavor of failure, without sprinkling nullable types and unchecked exceptions everywhere.

sealed class ApiResult<out T> {
    data class Success<out T>(val data: T) : ApiResult<T>()
    data class HttpError(val code: Int, val message: String?) : ApiResult<Nothing>()
    data class NetworkError(val throwable: Throwable) : ApiResult<Nothing>()
    data class SerializationError(val throwable: Throwable) : ApiResult<Nothing>()
}

suspend fun fetchUser(id: String): ApiResult<User> = try {
    val response = api.getUser(id)
    if (response.isSuccessful && response.body() != null) {
        ApiResult.Success(response.body()!!)
    } else {
        ApiResult.HttpError(response.code(), response.message())
    }
} catch (e: IOException) {
    ApiResult.NetworkError(e)
} catch (e: SerializationException) {
    ApiResult.SerializationError(e)
}

Because ApiResult is sealed, a when on it must be exhaustive:

fun render(result: ApiResult<User>) = when (result) {
    is ApiResult.Success -> showUser(result.data)
    is ApiResult.HttpError -> showError("HTTP ${result.code}")
    is ApiResult.NetworkError -> showError("Check your connection")
    is ApiResult.SerializationError -> showError("Bad response format")
}

This is one of the best examples of sealed classes preventing “forgotten branch” bugs. The compiler forces you to handle every case.

2. Paging and streaming data states

Modern apps rely heavily on infinite scroll and streaming data. Libraries like Android’s Paging 3 already use sealed types internally, and you can mirror that pattern in your domain layer.

sealed class PageResult<out T> {
    data class Page<out T>(val items: List<T>, val nextKey: String?) : PageResult<T>()
    object EndOfList : PageResult<Nothing>()
    data class Error(val throwable: Throwable) : PageResult<Nothing>()
}

suspend fun loadNextPage(key: String?): PageResult<Article> { /* ... */ }

The advantage over a simple nullable approach is that EndOfList is explicit. There’s no guessing whether null means “no more data” or “we messed up the API call.” This is a clean example of how sealed classes encode meaning directly in the type system.


UI state: examples include screens, dialogs, and loading flows

The second cluster of examples of 3 practical examples of Kotlin sealed classes lives in UI state management. With Jetpack Compose and unidirectional data flow, sealed classes shine.

3. Screen state in Jetpack Compose or XML-based UIs

A common pattern in 2024–2025 Android apps is a single UiState sealed class per screen.

sealed class UserProfileUiState {
    object Loading : UserProfileUiState()
    data class Content(val user: User) : UserProfileUiState()
    data class Error(val message: String, val canRetry: Boolean) : UserProfileUiState()
}

@Composable
fun UserProfileScreen(state: UserProfileUiState) {
    when (state) {
        UserProfileUiState.Loading -> LoadingView()
        is UserProfileUiState.Content -> ProfileContent(state.user)
        is UserProfileUiState.Error -> ErrorView(state.message, state.canRetry)
    }
}

This is a textbook example of a sealed class replacing a messy combination of booleans like isLoading, errorMessage, and user. You can’t accidentally show a loading spinner and content at the same time, because that state simply doesn’t exist in the type.

4. Dialogs and bottom sheets as sealed UI events

Another realistic example of sealed classes in UI is controlling dialogs and bottom sheets.

sealed class DialogState {
    object None : DialogState()
    object ConfirmLogout : DialogState()
    data class DeleteItem(val itemName: String) : DialogState()
}

@Composable
fun AppDialogs(state: DialogState, onDismiss: () -> Unit) {
    when (state) {
        DialogState.None -> Unit
        DialogState.ConfirmLogout -> ConfirmLogoutDialog(onDismiss)
        is DialogState.DeleteItem -> DeleteItemDialog(state.itemName, onDismiss)
    }
}

Here, the sealed class becomes a single source of truth for all transient dialog states. This pattern scales nicely as your app adds more dialogs over time.

5. Navigation destinations in a single-activity app

Navigation is another area where examples of 3 practical examples of Kotlin sealed classes appear all the time.

sealed class Destination(val route: String) {
    object Home : Destination("home")
    data class Details(val id: String) : Destination("details/{id}")
    object Settings : Destination("settings")
}

fun NavController.navigateTo(destination: Destination) {
    when (destination) {
        Destination.Home -> navigate("home")
        is Destination.Details -> navigate("details/${destination.id}")
        Destination.Settings -> navigate("settings")
    }
}

Compared to stringly-typed routes, this pattern is far safer. You can’t accidentally navigate to a route that doesn’t exist, and you can’t forget the required arguments.


Domain modeling: errors, payments, and analytics

The third group of examples of 3 practical examples of Kotlin sealed classes lives in the domain layer—places where you care about business meaning, not just UI or transport.

6. Domain-specific error handling

Instead of throwing generic exceptions, you can represent domain errors as sealed classes that calling code must handle.

sealed class PaymentError : Throwable() {
    object InsufficientFunds : PaymentError()
    object CardExpired : PaymentError()
    object FraudSuspected : PaymentError()
    data class Unknown(val cause: Throwable) : PaymentError()
}

sealed class PaymentResult {
    object Success : PaymentResult()
    data class Failure(val error: PaymentError) : PaymentResult()
}

suspend fun chargeCard(request: PaymentRequest): PaymentResult { /* ... */ }

Now your checkout flow can respond precisely:

when (val result = chargeCard(request)) {
    PaymentResult.Success -> showReceipt()
    is PaymentResult.Failure -> when (result.error) {
        PaymentError.InsufficientFunds -> showTopUpPrompt()
        PaymentError.CardExpired -> showUpdateCardScreen()
        PaymentError.FraudSuspected -> showSupportContact()
        is PaymentError.Unknown -> showGenericError()
    }
}

This is one of the best examples of Kotlin sealed classes improving real business logic, not just making syntax prettier.

7. Analytics and logging events

Modern apps log a lot of events—to internal dashboards, to tools like Firebase Analytics, or to custom data platforms. Using sealed classes for events gives you type-safe tracking.

sealed class AnalyticsEvent {
    data class ScreenView(val name: String) : AnalyticsEvent()
    data class ButtonClick(val id: String) : AnalyticsEvent()
    data class Purchase(val productId: String, val amountCents: Long) : AnalyticsEvent()
}

interface AnalyticsTracker {
    fun track(event: AnalyticsEvent)
}

class FirebaseAnalyticsTracker(/* ... */) : AnalyticsTracker {
    override fun track(event: AnalyticsEvent) {
        when (event) {
            is AnalyticsEvent.ScreenView -> logScreen(event.name)
            is AnalyticsEvent.ButtonClick -> logClick(event.id)
            is AnalyticsEvent.Purchase -> logPurchase(event.productId, event.amountCents)
        }
    }
}

Later, if your data team adds a new required field to Purchase, the compiler will guide you to update all the tracking code.

8. Authentication flows and user sessions

Authentication flows are another area where examples of 3 practical examples of Kotlin sealed classes fit perfectly.

sealed class AuthState {
    object LoggedOut : AuthState()
    object Loading : AuthState()
    data class LoggedIn(val userId: String, val token: String) : AuthState()
    data class Error(val message: String) : AuthState()
}

class AuthViewModel(/* ... */) {
    private val _state = MutableStateFlow<AuthState>(AuthState.LoggedOut)
    val state: StateFlow<AuthState> = _state

    fun login(username: String, password: String) {
        _state.value = AuthState.Loading
        // ... perform login, then update state to LoggedIn or Error
    }
}

This pattern is now standard in many Android samples and production apps, especially those using Kotlin coroutines and StateFlow.


Why sealed classes instead of enums or interfaces?

Looking at these real examples of 3 practical examples of Kotlin sealed classes, a natural question is: why not enums or plain interfaces?

Enums work well when:

  • You only need constant values with minimal data
  • The set of options is small and not expected to grow complex

But enums fall short when each case needs different data. For example, PaymentError.CardExpired and PaymentError.Unknown(cause) have very different payloads. With enums, you’d end up with nullable fields or parallel maps.

Interfaces are flexible but too open. Any file can implement an interface, so the compiler can’t guarantee you’ve handled all implementations in a when. With sealed classes, all subclasses must be in the same file (or module, with sealed interface + permits in newer Kotlin), so the compiler can enforce exhaustiveness.

Kotlin’s official documentation on sealed classes explains this closed-hierarchy behavior in more detail: https://kotlinlang.org/docs/sealed-classes.html


A few trends make these examples of 3 practical examples of Kotlin sealed classes even more relevant in 2024–2025:

  • Kotlin Multiplatform: Shared models for UI state and domain errors benefit heavily from sealed classes, because they compile to multiple targets (Android, iOS, desktop) with the same type safety.
  • Jetpack Compose & Compose Multiplatform: Declarative UIs love explicit state. Sealed classes fit right into unidirectional data flow.
  • Structured concurrency with coroutines: When you combine flows, channels, and sealed event types, your async code becomes a lot more predictable.

If you care about correctness and maintainability—especially in regulated domains like finance or health—modeling state explicitly is not just a nicety. For example, when dealing with health-related apps that must respect privacy and safety guidelines, clear state machines and explicit error types make audits and testing far easier. While not Kotlin-specific, organizations like the U.S. National Institute of Standards and Technology (NIST) publish guidance on secure software engineering that aligns well with these ideas: https://www.nist.gov/itl


FAQ about Kotlin sealed classes with real examples

What are some real examples of Kotlin sealed classes in Android apps?

Real examples include:

  • UiState sealed classes for screens (Loading, Content, Error)
  • Navigation destinations as sealed types with routes and arguments
  • Network result wrappers like ApiResult.Success and ApiResult.HttpError
  • Domain-specific errors such as PaymentError.InsufficientFunds
  • Analytics events like AnalyticsEvent.ScreenView and AnalyticsEvent.Purchase

These map directly to the examples of 3 practical examples of Kotlin sealed classes shown earlier.

Can sealed classes replace the Result type from the Kotlin standard library?

Sometimes. The standard Result<T> is great for simple success/failure flows. But when you need richer error detail or multiple failure modes, defining your own sealed class (like ApiResult<T> or PaymentResult) gives you clearer, self-documenting code.

Are sealed classes good for modeling UI navigation?

Yes. A sealed Destination hierarchy is a clean example of how to avoid stringly-typed navigation. Each destination can carry typed arguments, and your when over Destination is checked at compile time.

Is there a downside to using sealed classes everywhere?

Overusing them can make your model layer noisy. If a state only has two simple options and no extra data, a Boolean or enum might be enough. Reach for sealed classes when:

  • Each case carries different data, or
  • You want the compiler to enforce exhaustive handling.

Where can I learn more beyond these examples?

Alongside the Kotlin docs, many university courses on programming languages and type systems discuss algebraic data types, which sealed classes approximate. For a broader view of type-safe design, you might explore material from institutions like MIT OpenCourseWare: https://ocw.mit.edu


Sealed classes are not magic, but the patterns above show why developers keep asking for examples of 3 practical examples of Kotlin sealed classes. They give you a way to encode real-world constraints directly into your types—so the compiler can help you keep your app’s behavior honest as it grows.

Explore More Kotlin Code Snippets

Discover more examples and insights in this category.

View All Kotlin Code Snippets