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 Tvariance withNothingfor 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
whenwith noelse out TandNothingfor 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
- The Result Type
- runCatching
- Sealed Result Hierarchies
- Functional Error Handling