Explanation of Sealed Classes in Kotlin

Posted on 2025-12-30 by Burak Hamdi TUFAN
General Programming
Explanation of Sealed Classes in Kotlin
Hello everyone, in this article we are going to discover Sealed classes which is a powerfull feature of Kotlin and also Java.

Lets get started.

Sealed classes are important feature in Kotlin that help you represent restricted class hierarchies. So all subclasses will be known at compile-time. They are particularly useful for modeling state machines, results or events where you want to ensure type safety in when expressions in Kotlin.

What Are Sealed Classes?

A sealed class is declared using the sealed modifier. Unlike regular classes, a sealed class can only be subclassed within the same file where it is declared. This restriction allows the compiler to know all possible subclasses and enabling Kotlin to perform exhaustive checks when you use sealed classes in when statements.

In essence, sealed classes give you a way to define a closed set of types, similar to enum types, but with more flexibility.

Let's make a simple example
Below we will represent the different states of a Request Result:

sealed class RequestResult {
    data class Success(val data: String) : Result()
    data class Error(val errorMessage: String) : Result()
    object Loading : Result()
}

RequestResult is a sealed class and Success, Error, and Loading are all subclasses that represent the possible states.

We can use above sealed class like below:

fun handleResult(result: RequestResult) {
    when (result) {
        is RequestResult.Success -> println("Data: ${result.data}")
        is RequestResult.Error -> println("Error: ${result.errorMessage}")
        is RequestResult.Loading -> println("Loading...")
    }
}
Notice how the when expression does not require an else branch because Kotlin knows all subclasses of RequestResult class, so the compiler ensures that all cases are covered.

What are the Advantages Sealed Classes:

  • when statements must handle all possible subclasses. In this case Sealed classes are useful.
  • You can easily model complex data structures with definen the exact patterns
  • Enums cannot hold multiple types or associated data, but sealed classes can. So sealed classes are better then enums
  • Only known and defined subclasses can represent the sealed hierarchy. So it is type safe.

When to use Sealed Classes

  • Representing UI states (e.g., Loading, Success, Error).
  • Representing results of operations (e.g., Success or Failure).
  • Modeling domain-specific closed hierarchies.

Sealed Interfaces

These work just like sealed classes instead of providing implementations, they define contracts that implementations must fulfill. This pattern is useful when multiple independent types need to represent different states but shouldn’t share implementation details

Below you can see an example of Sealed interface definition:

sealed interface UiState

data class Loading(val progress: Int) : UiState
data class Display(val content: String) : UiState
data class Error(val error: String) : UiState

Nested Sealed Classes

You can nest sealed classes within other classes to create localized hierarchies. For example, you might have sealed classes inside a ViewModel to represent UI events.


class LoginViewModel {
    sealed class LoginEvent {
        object LoginStarted : LoginEvent()
        data class LoginSuccess(val userName: String) : LoginEvent()
        data class LoginFailed(val reason: String) : LoginEvent()
    }

    fun onLogin(event: LoginEvent) {
        when (event) {
            is LoginEvent.LoginStarted -> println("Logging in...")
            is LoginEvent.LoginSuccess -> println("Welcome ${event.userName}")
            is LoginEvent.LoginFailed -> println("Login failed: ${event.reason}")
        }
    }
}

As you can see above, all possible login events are clearly defined within the LoginEvent sealed class. The compiler enforces completeness when handling events.

Let's compare Sealed Classes and Enums

FeatureSealed ClassEnum
Data per type Can hold arbitrary data (via data classes) Limited to fixed constants
Subclass flexibility Supports inheritance and complex hierarchies Each enum constant is a singleton
Comprehensiveness Checked by compiler Checked by compiler
Use cases Represent states and results Represent fixed set of constant values

Combining Sealed Classes With Generics

You can also use generics with sealed classes for type safety and flexibility. This is a very common pattern in Kotlin-based applications—especially in frameworks like Android’s ViewModel or networking layers using coroutines


sealed class Result<out T> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Failure(val exception: Throwable) : Result<Nothing>()
}

fun <T> processResult(result: Result<T>) {
    when (result) {
        is Result.Success -> println("Processed data: ${result.data}")
        is Result.Failure -> println("Error: ${result.exception.message}")
    }
}

Now lets make a Real-World Example

Here we are going to build an API response handler sealed class with a template. Here’s how sealed classes can make your code cleaner in an API-driven app:

// Define a Sealed class with template as Api response with states.
sealed class ApiResponse<out T> {
    data class Success<out T>(val data: T) : ApiResponse<T>()
    data class Error(val message: String) : ApiResponse<Nothing>()
    object Loading : ApiResponse<Nothing>()
}

// Define a suspend function which will perform api call at background with coroutines. 
suspend fun fetchUserData(): ApiResponse<String> {
    return try {
        ApiResponse.Success("User data received!")
    } catch (e: Exception) {
        ApiResponse.Error(e.message ?: "Unknown error")
    }
}
After we define above structure, in below code block we will use above sealed class to perform api call and check the result in when function.

val response = fetchUserData()

when (response) {
    is ApiResponse.Success -> println("Data: ${response.data}")
    is ApiResponse.Error -> println("Error occurred: ${response.message}")
    ApiResponse.Loading -> println("Fetching...")
}

Final Words

Sealed classes in Kotlin are an elegant and type-safe way to represent restricted hierarchies. They improve code readability and comprehensiveness. They prevent runtime errors caused by unhandled states.

That is all.

Burak Hamdi TUFAN


Tags
Share this Post
Send with Whatsapp