SyntaxStudy
Sign Up
Kotlin Sealed Classes and Algebraic Data Types
Kotlin Beginner 1 min read

Sealed Classes and Algebraic Data Types

Sealed classes restrict the class hierarchy: all direct subclasses must be declared in the same file (or in Kotlin 1.5+, the same package). This gives the compiler complete knowledge of the hierarchy, allowing when expressions to be exhaustive — the compiler can verify that every case is handled. Sealed classes are the idiomatic way to model sum types (also known as algebraic data types or discriminated unions) in Kotlin. A common pattern is a sealed class representing the result of an operation with subclasses for success and various failure cases. Sealed interfaces (introduced in Kotlin 1.5) extend this concept to interfaces, allowing a single type to appear as both an interface and a sealed hierarchy — useful when you want your sealed type to also implement an existing interface.
Example
// Network result as sealed class hierarchy
sealed class NetworkResult<out T> {
    data class Success<T>(val data: T)        : NetworkResult<T>()
    data class Error(val code: Int, val msg: String) : NetworkResult<Nothing>()
    object Loading                             : NetworkResult<Nothing>()
}

data class Post(val id: Int, val title: String)

fun fetchPost(id: Int): NetworkResult<Post> = when (id) {
    0    -> NetworkResult.Loading
    else -> if (id > 0)
                NetworkResult.Success(Post(id, "Post #$id"))
            else
                NetworkResult.Error(404, "Not found")
}

// Exhaustive when expression — compiler ensures all cases covered
fun render(result: NetworkResult<Post>): String = when (result) {
    is NetworkResult.Loading       -> "Loading..."
    is NetworkResult.Success       -> "Post: ${result.data.title}"
    is NetworkResult.Error         -> "Error ${result.code}: ${result.msg}"
}

// Sealed interface
sealed interface Expr
data class Num(val value: Double)         : Expr
data class Add(val l: Expr, val r: Expr)  : Expr
data class Mul(val l: Expr, val r: Expr)  : Expr

fun eval(e: Expr): Double = when (e) {
    is Num -> e.value
    is Add -> eval(e.l) + eval(e.r)
    is Mul -> eval(e.l) * eval(e.r)
}

fun main() {
    listOf(-1, 0, 5).map(::fetchPost).map(::render).forEach(::println)

    val expr = Mul(Add(Num(2.0), Num(3.0)), Num(4.0))
    println("Result: ${eval(expr)}")   // 20.0
}