skip to content

Sealed vs Enum

Reach for an enum when the cases are fixed constants with the same shape, and for a sealed hierarchy when each case carries different data or needs multiple instances. Being able to justify the choice, and to convert between them, is what the question is really testing.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

What is the core difference between a Kotlin enum class and a sealed class/interface, and when would you reach for each?

level: juniorimportance: must knowfreq 80%

answer

  1. enum = fixed singletons, same shape
  2. sealed = closed hierarchy, varied data, many instances
  3. both => exhaustive when, no else
  4. enum gets values()/entries/ordinal/valueOf
  5. nullable fields on enum = switch to sealed

basics

~20 s

An 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 s

An `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 lines
kotlin
enum 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

for a junior

States the headline distinction: enum = fixed constants, sealed = closed set of varied subclasses; both give exhaustive when.

for a middle

Articulates instance-count and per-case-data differences and gives the nullable-field smell test for converting enum to sealed.

for a senior

Frames sealed as algebraic data types, notes module/package locality rules, and weighs enum built-ins (ordinal/entries/EnumMap) against sealed flexibility.

for a principal

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

context

open as a page

You have an enum where entries are sprouting nullable fields to hold case-specific data. How do you convert it to a sealed hierarchy, and what do you gain and lose?

level: middleimportance: should knowfreq 60%

basics

~20 s

Turn each enum entry into its own subclass of a sealed type. Cases that carry data become data classes; data-free cases become objects. You gain per-case fields and type-safe access; you lose enum freebies like ordinal, valueOf, and entries.

open as a page

In a sealed hierarchy, when should a case be an object/data object versus a data class, and how does that map back to enum entries?

level: middleimportance: should knowfreq 45%

basics

~20 s

Use an object (or data object) when the case carries no data — it's a singleton like an enum entry. Use a data class when the case needs its own fields and may have many instances. data object adds a clean toString and equals.

open as a page

Explain how 'singleton constants' for enums versus 'closed set of subtypes' for sealed types affects instance count, identity, and exhaustiveness guarantees the compiler can give.

level: seniorimportance: should knowfreq 35%

basics

~20 s

Enum entries are single shared instances, so you can compare them by identity and use them as map keys cheaply. Sealed subtypes can have unlimited instances. Both let the compiler verify a when covers every case, but it counts entries for enums and subtypes for sealed.

open as a page

For a public API value type with a small fixed set of cases, what trade-offs decide between an enum and a sealed type, especially around serialization and evolution?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Enums serialize simply by name and are easy to evolve by adding constants, but every case must share one shape. Sealed types let cases carry different data and add type-safety, but need explicit polymorphic serialization and are harder to extend across module boundaries.

open as a page