Kotlin Sealed Classes

A sealed class is a restricted class hierarchy — all subclasses must be defined in the same file. This gives you a fixed set of possible types. Sealed classes are ideal for representing states, results, or events where every possibility is known in advance.

Defining a Sealed Class

sealed class PaymentResult {
    data class Success(val transactionId: String, val amount: Double) : PaymentResult()
    data class Failure(val errorCode: Int, val reason: String) : PaymentResult()
    object Pending : PaymentResult()
}

fun processPayment(result: PaymentResult) {
    when (result) {
        is PaymentResult.Success  -> println("Paid ₹${result.amount} | Txn: ${result.transactionId}")
        is PaymentResult.Failure  -> println("Failed [${result.errorCode}]: ${result.reason}")
        is PaymentResult.Pending  -> println("Payment is being processed...")
    }
}

Why Sealed Classes Are Powerful


Sealed class guarantees ALL cases are covered:

when (result) {
    is Success  → handle
    is Failure  → handle
    is Pending  → handle
    // No "else" needed — compiler verifies all cases!
}

With a regular class:
  when (result) {
      // What if someone adds a new subclass in another file?
      // Your when might miss it. Bug!
  }

Sealed Class as a State Machine

sealed class NetworkState {
    object Idle : NetworkState()
    object Loading : NetworkState()
    data class Success(val data: String) : NetworkState()
    data class Error(val message: String) : NetworkState()
}

fun render(state: NetworkState): String = when (state) {
    NetworkState.Idle     -> "Waiting for request..."
    NetworkState.Loading  -> "Loading, please wait..."
    is NetworkState.Success -> "Data: ${state.data}"
    is NetworkState.Error   -> "Error: ${state.message}"
}

fun main() {
    val states = listOf(
        NetworkState.Idle,
        NetworkState.Loading,
        NetworkState.Success("User list loaded"),
        NetworkState.Error("Connection timeout")
    )
    states.forEach { println(render(it)) }
}

Output:

Waiting for request...
Loading, please wait...
Data: User list loaded
Error: Connection timeout

Sealed Class Diagram


         PaymentResult (sealed)
               │
       ┌───────┼───────┐
       │       │       │
    Success  Failure  Pending
    (data    (data    (object
    class)   class)   singleton)

Sealed Class vs Enum


                  Enum           Sealed Class
─────────────────────────────────────────────────────
Each variant holds data?  NO (shared)    YES (own fields)
Each variant is different type?   NO     YES
Subclassing?          NO             YES
Pattern matching?     YES            YES

Practical Example: Form Validation

sealed class ValidationResult {
    object Valid : ValidationResult()
    data class Invalid(val errors: List) : ValidationResult()
}

fun validateEmail(email: String): ValidationResult {
    val errors = mutableListOf()
    if (!email.contains("@")) errors.add("Missing @ symbol")
    if (!email.contains(".")) errors.add("Missing domain extension")
    if (email.length < 5)    errors.add("Email too short")
    return if (errors.isEmpty()) ValidationResult.Valid
           else ValidationResult.Invalid(errors)
}

fun main() {
    val emails = listOf("alice@mail.com", "notanemail", "x@y")
    emails.forEach { email ->
        when (val result = validateEmail(email)) {
            ValidationResult.Valid         -> println("✓ $email is valid")
            is ValidationResult.Invalid   -> {
                println("✗ $email is invalid:")
                result.errors.forEach { println("  - $it") }
            }
        }
    }
}

Output:

✓ alice@mail.com is valid
✗ notanemail is invalid:
  - Missing @ symbol
✗ x@y is invalid:
  - Email too short

Leave a Comment

Your email address will not be published. Required fields are marked *