What is the core difference between a Kotlin enum class and a sealed class/interface, and when would you reach for each?
answer
- enum = fixed singletons, same shape
- sealed = closed hierarchy, varied data, many instances
- both => exhaustive when, no else
- enum gets values()/entries/ordinal/valueOf
- nullable fields on enum = switch to sealed
basics
~20 sAn enum is a fixed set of named constant objects that all look the same. A sealed type is a closed set of subclasses that can each have different fields and many instances. Use enum for simple constants, sealed for varied data.
solid answer
~50 sAn `enum class` defines a fixed, finite set of singleton constants — each entry exists exactly once and shares the same property shape (though entries can override members). A `sealed class` or `sealed interface` defines a closed hierarchy: the compiler knows all direct subtypes (in the same module/package), but each subtype is a normal class that can carry its own distinct properties and have unlimited instances. Reach for an enum when you model a small, fixed list of constants like `Direction.NORTH` or a status flag. Reach for a sealed type when each case needs different data — e.g. `Result.Success(data)` vs `Result.Error(throwable)` — or when you need an algebraic data type / state machine. Both give exhaustive `when` without an `else`. Key signal: same shape and one-of-each => enum; varied per-case payload and many instances => sealed.
code
kotlin · 10 linesenum class Status { ACTIVE, SUSPENDED }
sealed interface Outcome
data class Success(val value: Int) : Outcome
data class Failure(val reason: String) : Outcome
fun render(o: Outcome) = when (o) {
is Success -> "ok=${o.value}"
is Failure -> "err=${o.reason}"
}go deeper
States the headline distinction: enum = fixed constants, sealed = closed set of varied subclasses; both give exhaustive when.
Articulates instance-count and per-case-data differences and gives the nullable-field smell test for converting enum to sealed.
Frames sealed as algebraic data types, notes module/package locality rules, and weighs enum built-ins (ordinal/entries/EnumMap) against sealed flexibility.
Discusses API/serialization and evolution trade-offs (stable ordinal/name vs. exhaustive compiler checks) and when each shapes a public contract or domain model.
## The two constructs **`enum class`** — a special class whose instances are a fixed, compile-time-known list of named constants. Each constant (`enum entry`) is a singleton: there is exactly ONE `Color.RED` for the whole program. All entries share the same type and the same set of properties, although individual entries may override functions or supply constructor arguments. ```kotlin enum class Planet(val massKg: Double) { EARTH(5.97e24), MARS(6.42e23); fun describe() = "$name has mass $massKg" } ``` **`sealed class` / `sealed interface`** — a closed type hierarchy. `sealed` means the set of *direct* subtypes is known at compile time (they must live in the same module, and for classes the same package). Each subtype is an ordinary class/object, so it can hold its **own** distinct properties and be instantiated **many** times. ```kotlin sealed interface Payment data class Card(val pan: String, val cvv: String) : Payment data class Cash(val amount: Long) : Payment object Free : Payment // a one-of-a-kind case uses 'object' ``` ## How they differ - **Instance count:** enum entries are singletons (one each); sealed subtypes can have unlimited instances (e.g. thousands of `Card` objects). - **Per-case data:** every enum entry has the *same* properties; sealed subtypes each define *their own* properties. - **Identity:** enums are great as map keys, in `EnumSet`/`EnumMap`, and have a stable `ordinal` and `name`; sealed cases do not. - **Built-ins:** enums get `values()` / `entries`, `valueOf()`, `ordinal`, `name` for free; sealed types do not. ## What they share Both enable an **exhaustive `when`** — the compiler verifies every case is handled, so no `else` branch is needed and adding a new case becomes a compile error you must fix. This is the main reason both are used for modeling closed sets. ## Decision rule - Fixed list of constants, all identical shape, one instance each, want `ordinal`/`valueOf`/serialize-by-name => **enum**. - Cases carry different payloads, or you need many instances per case, or you want an algebraic data type (ADT) / state model => **sealed**. ## Quick smell test If you find yourself wanting to attach different fields to different enum entries (forcing nullable properties everywhere), that's the signal to switch to a sealed type.
- Can an enum entry hold data unique to itself?Not cleanly — all entries share one property set, so unique data forces nullable/unused fields. That's exactly when sealed wins.
- Do both require module/package locality for their cases?Enum entries live inside the enum body. Sealed subtypes must be in the same module (same package for sealed classes), enforced by the compiler.
Enum is a fixed menu of identical buttons; a sealed type is a closed family of differently-shaped forms.
saying these in an interview costs you the question
- Saying enum entries can each carry arbitrary different fields like sealed cases
- Claiming sealed types provide values()/ordinal/valueOf
- Saying you always need an else branch in when for both
- Thinking sealed cases are singletons like enum entries