0Pricing
Kotlin Academy · Lesson

Sealed Result Hierarchies

Custom result types.

Sealed Result Hierarchies is a free Kotlin Academy lesson on CoddyKit — lesson 3 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.

Beyond the Built-in Result

Kotlin's Result only carries an exception on failure. Often you want richer, typed error information. A sealed class hierarchy lets you model exactly the outcomes your domain has.

A Sealed Outcome

Define a sealed type with success and error subclasses. The compiler knows all cases, enabling exhaustive handling.

sealed class Outcome<out T> {
    data class Ok<T>(val value: T) : Outcome<T>()
    data class Err(val message: String) : Outcome<Nothing>()
}

fun main() {
    val r: Outcome<Int> = Outcome.Ok(5)
    println(r)
}

Exhaustive when

Because the type is sealed, a when over it needs no else, and the compiler enforces handling every case.

sealed class Outcome<out T> {
    data class Ok<T>(val value: T) : Outcome<T>()
    data class Err(val message: String) : Outcome<Nothing>()
}

fun describe(o: Outcome<Int>) = when (o) {
    is Outcome.Ok -> "value " + o.value
    is Outcome.Err -> "error " + o.message
}

fun main() {
    println(describe(Outcome.Ok(9)))
    println(describe(Outcome.Err("boom")))
}

Typed Error Variants

Model distinct failure kinds as separate subclasses, each carrying relevant data. This beats a single opaque exception.

sealed class Fetch<out T> {
    data class Success<T>(val data: T) : Fetch<T>()
    data class NotFound(val id: Int) : Fetch<Nothing>()
    data class Network(val code: Int) : Fetch<Nothing>()
}

fun main() {
    val r: Fetch<String> = Fetch.NotFound(42)
    println(r)
}

Handling Each Variant

A when can react specifically to each error type, with full data available for each branch.

sealed class Fetch<out T> {
    data class Success<T>(val data: T) : Fetch<T>()
    data class NotFound(val id: Int) : Fetch<Nothing>()
    data class Network(val code: Int) : Fetch<Nothing>()
}

fun handle(f: Fetch<String>) = when (f) {
    is Fetch.Success -> "data: " + f.data
    is Fetch.NotFound -> "missing id " + f.id
    is Fetch.Network -> "net error " + f.code
}

fun main() {
    println(handle(Fetch.Network(503)))
}

The Nothing Trick

Error variants that hold no success value use Outcome<Nothing>. Since Nothing is a subtype of every type and the generic is out, an error fits any Outcome<T>.

sealed class Res<out T> {
    data class Ok<T>(val v: T) : Res<T>()
    object Empty : Res<Nothing>()
}

fun get(flag: Boolean): Res<String> =
    if (flag) Res.Ok("hi") else Res.Empty

fun main() {
    println(get(false))
}

Object for Stateless Cases

If an error variant carries no data, declare it as an object singleton instead of a class.

sealed class Login {
    data class Success(val user: String) : Login()
    object WrongPassword : Login()
    object Locked : Login()
}

fun main() {
    val r: Login = Login.WrongPassword
    println(r === Login.WrongPassword)
}

Returning the Hierarchy

Functions return the sealed type; callers must address every outcome, eliminating forgotten error paths.

sealed class Parsed {
    data class Num(val value: Int) : Parsed()
    data class Bad(val input: String) : Parsed()
}

fun parse(s: String): Parsed {
    val n = s.toIntOrNull()
    return if (n != null) Parsed.Num(n) else Parsed.Bad(s)
}

fun main() {
    when (val r = parse("x")) {
        is Parsed.Num -> println(r.value)
        is Parsed.Bad -> println("bad: " + r.input)
    }
}

Sealed vs Built-in Result

Use the built-in Result for quick, exception-based capture. Use a custom sealed hierarchy when you need multiple typed error cases with domain-specific data and exhaustive handling.

Designing the Hierarchy

Good sealed result design:

  • One success variant carrying the payload
  • One subclass per meaningful failure mode
  • out T variance with Nothing for error-only cases

Compiler as Safety Net

The biggest win: when you add a new variant later, every non-exhaustive when becomes a compile error, pointing you to each place that must handle it. Errors cannot silently slip through.

Quick Check

Test your understanding of sealed result hierarchies.

Recap

You built sealed result hierarchies:

  • Success plus typed error variants
  • Exhaustive when with no else
  • out T and Nothing for error-only cases
  • Compiler-enforced handling of new variants

Next: functional error handling with map and recover.

Frequently asked questions

Is the “Sealed Result Hierarchies” lesson free?

Yes — the full text of “Sealed Result 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 “Sealed Result Hierarchies”?

Custom result 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 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Sealed Result 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. The Result Type
  2. runCatching
  3. Sealed Result Hierarchies
  4. Functional Error Handling
← Back to Kotlin Academy