0Pricing
Kotlin Academy · Lesson

Nesting and Combining Sealed Hierarchies

Build complex state machines by nesting sealed types.

Nesting and Combining Sealed Hierarchies is a free Kotlin Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Kotlin Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Composing State Trees

Real-world state often has multiple dimensions. Nest sealed types or combine separate hierarchies to model complex domains cleanly.

Nested Sealed Class

Declare child sealed types inside an outer sealed type. The compiler still enforces exhaustiveness across the full tree.

sealed class UiState {
    object Loading : UiState()
    sealed class Loaded : UiState() {
        data class Success(val items: List<String>) : Loaded()
        data class Empty(val message: String) : Loaded()
    }
    data class Error(val msg: String) : UiState()
}
fun render(s: UiState) = when (s) {
    UiState.Loading -> "loading"
    is UiState.Loaded.Success -> "${s.items.size} items"
    is UiState.Loaded.Empty -> "empty: ${s.message}"
    is UiState.Error -> "err: ${s.msg}"
}
fun main() {
    println(render(UiState.Loaded.Success(listOf("a", "b"))))
}

Sealed Interfaces for Mixins

Use sealed interfaces when a state has multiple orthogonal aspects — each aspect a different interface.

sealed interface Authenticated
data class User(val id: Int) : Authenticated
object Guest
sealed interface Permission
object Read : Permission
object Write : Permission
fun describe(a: Authenticated, p: Permission): String = when {
    a is User && p is Write -> "user ${a.id} can write"
    a is User -> "user ${a.id} can read"
    else -> "?"
}
fun main() {
    println(describe(User(1), Write))
}

Combining Hierarchies

Pass two sealed values and switch on their combination — use guarded when.

sealed class Theme { object Light : Theme(); object Dark : Theme() }
sealed class Lang { object En : Lang(); object Tr : Lang() }
fun greet(theme: Theme, lang: Lang) = when {
    theme is Theme.Dark && lang is Lang.Tr -> "Karanlik Merhaba"
    theme is Theme.Light && lang is Lang.Tr -> "Aydinlik Merhaba"
    theme is Theme.Dark -> "Dark Hello"
    theme is Theme.Light -> "Light Hello"
    else -> "?"
}
fun main() {
    println(greet(Theme.Dark, Lang.Tr))
}

State Machine with Nested Variants

Model multi-step workflows as nested sealed types — each step has its own variants.

sealed class Checkout {
    object Start : Checkout()
    sealed class Payment : Checkout() {
        object Pending : Payment()
        data class Failed(val reason: String) : Payment()
        object Captured : Payment()
    }
    object Shipped : Checkout()
    object Delivered : Checkout()
}
fun describe(c: Checkout) = when (c) {
    Checkout.Start -> "starting"
    Checkout.Payment.Pending -> "awaiting payment"
    is Checkout.Payment.Failed -> "payment failed: ${c.reason}"
    Checkout.Payment.Captured -> "paid"
    Checkout.Shipped -> "shipped"
    Checkout.Delivered -> "delivered"
}
fun main() {
    println(describe(Checkout.Payment.Failed("card declined")))
}

Reusing Sealed Hierarchies Across Modules

Sealed types are best defined per module. For cross-module composition, expose a sealed interface so each module can add its own implementations.

sealed interface Notification
data class Email(val to: String) : Notification
data class Sms(val phone: String) : Notification
fun send(n: Notification) = when (n) {
    is Email -> "sending email to ${n.to}"
    is Sms -> "texting ${n.phone}"
}
fun main() {
    println(send(Email("a@example.com")))
}

Common Base Properties

Sealed classes can declare common properties — all subtypes inherit them. Keeps state and metadata DRY.

sealed class HttpResponse(val code: Int) {
    class Ok(val body: String) : HttpResponse(200)
    class NotFound(val path: String) : HttpResponse(404)
    class ServerError(val cause: String) : HttpResponse(500)
}
fun describe(r: HttpResponse) = "[${r.code}] " + when (r) {
    is HttpResponse.Ok -> r.body
    is HttpResponse.NotFound -> "not found: ${r.path}"
    is HttpResponse.ServerError -> "fail: ${r.cause}"
}
fun main() {
    println(describe(HttpResponse.Ok("hello")))
}

Modeling Recursive Trees

Sealed types describe recursive structures like expression trees naturally.

sealed class Expr {
    data class Num(val value: Int) : Expr()
    data class Add(val left: Expr, val right: Expr) : Expr()
    data class Mul(val left: Expr, val right: Expr) : Expr()
}
fun eval(e: Expr): Int = when (e) {
    is Expr.Num -> e.value
    is Expr.Add -> eval(e.left) + eval(e.right)
    is Expr.Mul -> eval(e.left) * eval(e.right)
}
fun main() {
    val expr = Expr.Add(Expr.Num(3), Expr.Mul(Expr.Num(4), Expr.Num(5)))
    println(eval(expr)) // 23
}

Combining with Generics

Sealed types can be generic, enabling typed result envelopes like Result<T>.

sealed class ApiResult<out T> {
    data class Success<T>(val data: T) : ApiResult<T>()
    data class Failure(val message: String) : ApiResult<Nothing>()
}
fun handle(r: ApiResult<String>) = when (r) {
    is ApiResult.Success -> "got ${r.data}"
    is ApiResult.Failure -> "err: ${r.message}"
}
fun main() {
    println(handle(ApiResult.Success("hi")))
    println(handle(ApiResult.Failure("nope")))
}

Anti-Pattern: Too-Deep Nesting

Deep nesting hides intent. If a sealed tree is more than 2 levels deep, consider flattening or extracting subtrees into their own files.

sealed class Top {
    sealed class Mid : Top() {
        sealed class Deep : Mid() {
            object Leaf : Deep()
        }
    }
}
fun main() {
    val l = Top.Mid.Deep.Leaf
    println(l::class.simpleName)
}

Combining via Composition

When two state dimensions are independent, prefer composition (a data class wrapping two sealed values) over deeply nested hierarchies.

sealed class Loading { object Active : Loading(); object Idle : Loading() }
sealed class Auth { data class Signed(val id: Int) : Auth(); object Anon : Auth() }
data class AppState(val loading: Loading, val auth: Auth)
fun main() {
    val s = AppState(Loading.Active, Auth.Signed(1))
    println(s)
}

Quick Check

What's the recommended approach when two state dimensions are independent?

Recap

Nest sealed types for tree-shaped state; combine independent sealed hierarchies with composition (wrapping data class). Add common properties to a sealed base, use generics for typed results, and model recursive structures naturally. Keep nesting shallow.

Frequently asked questions

Is the “Nesting and Combining Sealed Hierarchies” lesson free?

Yes — the full text of “Nesting and Combining Sealed Hierarchies” is free to read here on the web, and the Kotlin Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Kotlin Academy course, upgrade to CoddyKit PRO.

What will I learn in “Nesting and Combining Sealed Hierarchies”?

Build complex state machines by nesting sealed types. You practise Kotlin Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Kotlin Academy?

No prior experience is required. Kotlin Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Nesting and Combining Sealed Hierarchies” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Kotlin Academy lesson?

Yes. Every Kotlin Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. sealed class vs sealed interface: When to Use Each
  2. Exhaustive when with Sealed Hierarchies
  3. Modeling UI State with Sealed Classes
  4. Nesting and Combining Sealed Hierarchies
← Back to Kotlin Academy