Sealed Interfaces

Sealed Classes and Sealed Interfaces

An enum fixes the instances; a sealed type fixes the subtypes. Only code in the same module and package may extend a sealed class or interface directly, so the compiler knows the complete list. Each subtype is an ordinary class: BookNest's cart actions need a book and a quantity to add, a book to remove, nothing to clear.

A sealed interface with data class and data object subtypes (Sealed.kt)Kotlin
sealed interface CartAction
data class Add(val bookId: Int, val quantity: Int = 1) : CartAction
data class Remove(val bookId: Int) : CartAction
data object Clear : CartAction
object Checkout : CartAction                       // plain object, for comparison
fun main() = listOf(Add(3, quantity = 2), Remove(1), Clear, Checkout).forEach(::println)
Output
Add(bookId=3, quantity=2)
Remove(bookId=1)
Clear
Checkout@2f92e0f4

A data object (stable since Kotlin 1.9.0) gives a singleton the readable toString() and consistent equals()/hashCode() of a data class, where a plain object prints a hash code. Prefer a sealed interface (Kotlin 1.5.0) unless subtypes share state or constructor logic, since a class may implement several. A sealed class is abstract, with protected or private constructors. Once cases carry different data, an enum no longer fits.