Sealed Class — What It Is, How It Works, and Its Application

Author: IT Sectr Published: 2026-05-26 Reading time: 8 min

Sealed Class is a special type of class in Kotlin that restricts the inheritance hierarchy to a fixed set of subtypes. All subclasses are declared in the same file and are known to the compiler, which allows using an exhaustive when block without a mandatory else branch. According to Kotlin Docs, 2026, sealed classes are a key mechanism for representing restricted hierarchies such as states, error types, and UI events.

Key Takeaways

  • Sealed Class — a class with a fixed set of subclasses declared in the same file.
  • Exhaustive when — the compiler checks that all subtypes are handled, eliminating forgotten else branches.
  • Sealed interface — Kotlin 1.5+ supports sealed interfaces for multiple inheritance.
  • Error hierarchy — sealed class is the standard way for type-safe error handling in Kotlin.
  • Difference from enum — each sealed class subclass can contain unique state and a different number of fields.

What Is a Sealed Class?

Sealed Class is a class in Kotlin marked with the sealed modifier. It defines a restricted type hierarchy: all possible subclasses are listed in the same file, and the compiler knows about each of them. This distinguishes a sealed class from a regular open class whose subclasses can be declared anywhere.

The main purpose of a sealed class is type-safe representation of a finite set of variants. Each subclass can have its own data structure, making sealed class more flexible than enum. At runtime, a sealed class is a regular abstract class; the compiler only imposes restrictions at compile time.

Sealed class is especially useful in Android app architecture: UI states, network request results, navigation events similar to Intents, and of course error hierarchies are typical use cases.

At compile time, sealed class is optimized into a jump table for when expressions, making it more performant than chains of if-else. Combined with data class, each subclass can contain not only state but also methods, allowing self-documenting domain models without boilerplate code.

Sealed class is also effective for representing state machines in mobile applications. Each state is a separate subclass with unique parameters, and transitions between states are controlled via when expressions. The compiler guarantees that all possible states are handled, eliminating runtime errors when changing UI state or business logic.

Sealed Class vs Enum: Key Differences

Beginning Kotlin developers often confuse sealed class with enum, since both restrict the set of values. However, there is a fundamental difference: enum is a set of constants of the same type, while sealed class is a hierarchy of different types.

When to choose enum

Enum is optimal when all variants are constants without additional structure. For example, days of the week, order statuses, or action types without parameters. Each enum value is a singleton with a fixed name.

When to choose sealed class

Sealed class is needed when each variant has its own data. For example, a network error contains a response code, a parsing error contains details, and an authorization error contains a message. Each sealed class subclass is a separate type with unique fields.

kotlin
// Enum — all variants of one type
enum class Status { LOADING, SUCCESS, ERROR }

// Sealed class — each variant with its own data
sealed class UiState<out T> {
    object Loading : UiState<Nothing>()
    data class Success<T>(val data: T) : UiState<T>()
    data class Error(val message: String) : UiState<Nothing>()
}

Sealed Interface vs Sealed Class

Since Kotlin 1.5, it is possible to declare sealed interface. This extends the sealed concept to interfaces: a sealed interface also has a fixed set of implementations but supports multiple inheritance.

When to use sealed interface

Sealed interface is convenient when subclasses need to implement multiple contracts simultaneously. For example, a UI event can be both clickable and trackable. With sealed class, you would have to choose one base class; with sealed interface, the subclass implements both.

Limitations of sealed class

Sealed class is a class, so each subclass can have only one parent. Sealed interface solves this problem but cannot hold state. The choice between them depends on the task: if shared logic with fields is needed — use sealed class; if contract flexibility is needed — use sealed interface.

kotlin
sealed interface ScreenEvent {
    data class Refresh(val force: Boolean) : ScreenEvent
    data class Navigate(val route: String) : ScreenEvent
    data class ShowError(val toast: String) : ScreenEvent
}

sealed interface AnalyticsEvent {
    val name: String
    val params: Map<String, Any>
}

// The subclass implements both interfaces
data class LoginClicked(
    override val name: String = "login_click",
    override val params: Map<String, Any> = emptyMap()
) : ScreenEvent, AnalyticsEvent

Sealed Class for Error Hierarchies in Mobile Development

One of the main uses of sealed class in mobile development is type-safe error hierarchies. Instead of throwing exceptions of different types or using a general Exception, sealed class collects all possible domain errors into a single type.

How to build an error hierarchy

Create a sealed class DomainError and list all failure types as subclasses. Each subclass contains only the data that is relevant for that particular error type. The compiler guarantees that when handling the error, you will not forget any variant.

Example: authentication error handling

Consider an app with authorization where different failure scenarios are possible: wrong password, account blocked, server issue. Sealed class combines them into a single type with exhaustive handling.

kotlin
sealed class AuthError {
    data class InvalidCredentials(
        val attempts: Int
    ) : AuthError()

    data class AccountBlocked(
        val until: Long
    ) : AuthError()

    data class NetworkFailure(
        val cause: Throwable
    ) : AuthError()

    object ServerError : AuthError()
}

fun handleError(error: AuthError): String = when (error) {
    is AuthError.InvalidCredentials ->
        "Remaining attempts: ${3 - error.attempts}"
    is AuthError.AccountBlocked ->
        "Access blocked until ${Date(error.until)}"
    is AuthError.NetworkFailure ->
        "Check connection: ${error.cause.localizedMessage}"
    AuthError.ServerError ->
        "Server temporarily unavailable"
}

Sealed Class Usage Patterns in Android

Sealed class has become a standard tool in Android application architecture. Let us look at three key patterns where sealed class is indispensable in mobile development.

  • UI State — representing a screen as a state machine: Loading, Content, Error. Each state contains its own data, and sealed class guarantees that all transitions are handled.
  • Navigation Event — sealed class instead of navigation constants: each screen is a separate subclass with route parameters. The compiler checks argument types.
  • Action/Intent — the Unidirectional Data Flow pattern uses sealed class to represent all actions a user can perform on a screen.

It is also worth noting the use of sealed class in Clean Architecture. Each layer (data, domain, presentation) uses sealed class for its error types, and mappers convert one sealed class to another. For example, DataError from the data layer is mapped to DomainError for business logic, and then to UiState for the presentation layer. This preserves type safety at all levels of the application and guarantees that no error remains unhandled.

Testing Sealed Class Hierarchies

Testing sealed class requires a special approach since each subclass is a separate type with its own state. It is recommended to write parameterized tests that iterate over all sealed class subclasses. This guarantees that when expressions cover all variants, including new ones added when extending the hierarchy.

For UI tests, sealed class as UiState allows checking the display of each state: Loading shows a spinner, Content shows data, Error shows an error message. Since sealed class is finite, test coverage of all states provides complete confidence in the correctness of UI logic.

Common Mistakes When Working with Sealed Class

Despite the simplicity of the concept, developers regularly make mistakes when designing sealed class hierarchies. Let us look at the main problems and how to avoid them.

  • Subclasses in different files — the compiler will not allow declaring a sealed class if subclasses are located outside the file. This restriction guarantees an exhaustive when.
  • Mixing sealed and open — a sealed class cannot be open at the same time. If an extensible hierarchy is needed, use a regular abstract class, but you will sacrifice exhaustiveness.
  • Excessive nesting — sealed class inside sealed class creates a deep hierarchy that is difficult to maintain. For simple scenarios, two levels are sufficient.
  • Forgotten else in when — if a sealed class from a library lacks exhaustiveness, the compiler will not warn about a missing branch. Add else only intentionally.

Frequently Asked Questions

Can a sealed class have abstract methods?

Yes, a sealed class can contain abstract methods, and each subclass is required to implement them. This is convenient when all variants should provide a common interface but with different execution logic.

Are sealed classes available in Java?

In Java 17+, sealed classes and interfaces with the sealed modifier were introduced. Android currently supports Java 17 partially, but in Kotlin projects, sealed class has been available since Kotlin 1.0 without restrictions.

Can a sealed class inherit from another sealed class?

Yes, one sealed class can be a subclass of another. The sealed class hierarchy remains finite: the compiler knows all subclasses at every level. This allows building detailed error classifications.

Does sealed class affect performance?

Sealed class does not create runtime overhead. The compiler optimizes when expressions with sealed classes into jump tables (tableswitch), which is faster than chains of if-else. Performance is identical to enum.

How to test sealed class hierarchies?

Each sealed class subclass is tested separately. Since sealed class is finite, you can write a parameterized test that iterates over all variants. This provides full coverage of when-block branches.

Summary

  • Sealed class — a class with a fixed set of subclasses declared in one file, enabling exhaustive when analysis at compile time.
  • Each sealed class subclass can have its own data structure — this is the main difference from enum, where all variants are constants of the same type.
  • Sealed interface (Kotlin 1.5+) supports multiple inheritance, sealed class supports only single inheritance. The choice depends on the need for shared state.
  • Sealed class is the standard mechanism for type-safe error hierarchies in Kotlin: each failure type is a separate subclass with relevant fields.
  • Main patterns in Android: UI State, Navigation Event, and Action/Intent — are built on sealed class to guarantee completeness of handling.
  • Avoid subclasses in different files, excessive nesting, and mixing sealed with open — this violates the contract of a finite hierarchy.

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also