QUICK START:HTMLCSSJavaScriptTypeScriptPythonSQLReactNext.jsNode.jsLinux & UbuntuKotlinSwiftC# / .NETJavaGoRustC++DSASystem DesignDevOpsCybersecurityAI / ML
Intermediate 22 min readModule: Module 4: OOP, Data Classes, Sealed Hierarchies & Pattern Matching

Sealed Hierarchies, Pattern Matching & Data Classes

Model complex domain states with compile-time safety using Sealed Classes, Sealed Interfaces, and exhaustive when pattern matching.

What You Will Learn in This Lesson

  • Why Sealed Classes and Sealed Interfaces represent Algebraic Data Types (Sum Types)
  • Writing exhaustive `when` pattern matching expressions without fragile `else` branches
  • Data classes with immutability, destructuring declarations, and `.copy()`
  • Companion objects for factory methods and static-like access

Introduction & Core Concept

Modeling state accurately is one of the most critical aspects of robust software architecture. In Kotlin, Sealed Classes and Sealed Interfaces represent restricted class hierarchies where all direct subclasses are known at compile time. This allows the compiler to guarantee that every possible state is handled in 'when' expressions without needing fallback 'else' blocks.
WHY DOES THIS MATTER IN THE REAL WORLD?

In UI architectures (MVI/MVVM) and backend domain models, states transition through distinct phases (Loading, Success, Error). Sealed hierarchies ensure that if a developer adds a new state, the compiler immediately flags every unhandled location across the entire project.

Syntax & Structure

kotlin
sealed interface NetworkResult<out T> {
data class Success<T>(val data: T) : NetworkResult<T>
data class Error(val message: String) : NetworkResult<Nothing>
object Loading : NetworkResult<Nothing>
}

Modeling Domain State with Sealed Interfaces and Exhaustive When

kotlin
kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Domain State Modeling with Sealed Interfaces
package com.kwasacademy.domain
sealed interface UIState<out T> {
object Idle : UIState<Nothing>
object Loading : UIState<Nothing>
data class Success<T>(val data: T, val timestamp: Long) : UIState<T>
data class Error(val code: Int, val message: String) : UIState<Nothing>
}
data class UserProfile(val id: String, val name: String, val role: String)
fun renderState(state: UIState<UserProfile>) {
// Exhaustive pattern matching - compiler verifies every case is covered!
val output = when (state) {
is UIState.Idle -> "Status: Waiting for user action."
is UIState.Loading -> "Status: Fetching secure data from backend..."
is UIState.Success -> "Status: Profile loaded -> ${state.data.name} (${state.data.role})"
is UIState.Error -> "Status: Error ${state.code} -> ${state.message}"
}
println(output)
}
fun main() {
val loadingState = UIState.Loading
val successState = UIState.Success(
data = UserProfile("usr_101", "Alex Developer", "Lead Architect"),
timestamp = System.currentTimeMillis()
)
renderState(loadingState)
renderState(successState)
}

Line-by-Line Technical Breakdown

1Data Class Copy Method: Data classes provide a `.copy()` method for immutability-preserving state updates: `val updatedUser = user.copy(role = "Senior Architect")`. This creates a new instance with specified fields changed while keeping all other properties identical.

Try It Yourself (Interactive Editor)

Modify the code in real-time and click Run to test live browser output and console logs.

Intelligent Code Runner & Live Sandbox[KOTLIN]
KOTLIN SOURCE EDITOR
Interactive Live Code

Common Mistakes & How to Avoid Them

#1: Adding a default 'else ->' branch to a when expression over a sealed hierarchy.

Using 'else ->' suppresses the compiler's exhaustiveness check. If a new state is added later, the compiler will not warn you that you forgot to implement it.

Incorrect / Antipattern
when (state) {
    is UIState.Success -> ...
    else -> println("Other")
}
Correct / Professional Solution
when (state) {
    is UIState.Idle -> ...
    is UIState.Loading -> ...
    is UIState.Success -> ...
    is UIState.Error -> ...
}

Industry Best Practices & Professional Standards

  • Model UI and domain state transitions using `sealed interface`.
  • Avoid `else` in `when` expressions on sealed types to preserve compile-time exhaustiveness guarantees.
  • Use `.copy()` on data classes for safe, immutable state mutations.

Lesson Summary & Core Takeaways

  • Sealed hierarchies restrict subclassing to the current package/module.
  • `when` expressions over sealed classes are exhaustively verified at compile time.
  • Data classes provide automatic structural equality and immutable `.copy()` capabilities.