skip to content

Why can enum constants be compared safely with `==`, and what does it mean that each constant is a singleton?

level: seniorimportance: should knowfreq 40%

answer

  1. each constant constructed once at class load
  2. every reference = same object
  3. == and === always agree for enums
  4. equals/hashCode are final, identity-based
  5. great for when, map keys, EnumSet/EnumMap

basics

~20 s

Each enum constant exists as exactly one object created once at class load. So referencing RED always gives the same instance, and == (which compares value) and === (which compares identity) both reliably tell constants apart.

solid answer

~40 s

Each enum constant is a **singleton**: the runtime constructs it exactly once during class initialization and every reference (`Color.RED`) yields that same object. Because of this, structural equality `==` and referential equality `===` give identical results for enums — `Color.RED == Color.RED` and `Color.RED === Color.RED` are both true, and two different constants are never equal. The inherited `equals`/`hashCode` from `kotlin.Enum` are final identity-based implementations, so you cannot (and shouldn't) override them. This singleton nature also makes enums ideal `when`-subjects (exhaustive, identity-cheap), valid map keys (stable hashCode), and safe across `==`. One caveat: the singleton guarantee is per class-loader/JVM; constants serialized and deserialized across processes are matched by `name`, not object identity.

code

kotlin · 6 lines
kotlin
enum class Light { RED, YELLOW, GREEN }

val x = Light.GREEN
val y = Light.valueOf("GREEN")
println(x == y)   // true
println(x === y)  // true — same singleton instance

go deeper

for a junior

Knows == works to compare enum constants.

for a middle

Explains that constants are singletons so == and === agree.

for a senior

Knows equals/hashCode are final identity-based, the cross-process name caveat, and EnumMap/EnumSet benefits.

for a principal

Designs identity/serialization contracts and chooses enum vs sealed objects based on identity and evolution needs.

## Each constant is a singleton When an enum class is loaded, the JVM constructs **each constant exactly once** and stores it. From then on, every mention of `Color.RED` returns **that same object**: ```kotlin enum class Color { RED, GREEN, BLUE } val a = Color.RED val b = Color.RED println(a === b) // true — identical object ``` There is no way to create another `RED`; the constructor is closed to outside code, so the set of instances is fixed. ## `==` vs `===` for enums - `==` is **structural equality** (calls `equals`). - `===` is **referential equality** (same object identity). For most classes these can differ. For enums they **always agree**, because `kotlin.Enum` provides **final, identity-based** `equals`/`hashCode`: two references are `equals` iff they are the same constant iff they are the same object. ```kotlin Color.RED == Color.RED // true Color.RED === Color.RED // true Color.RED == Color.GREEN // false ``` This is why you can freely use `==` in `when` and `if` over enums without worrying about a broken `equals`. ## You cannot override equals/hashCode Because `kotlin.Enum.equals` and `hashCode` are **final**, the compiler forbids overriding them. That guarantees the identity semantics and keeps enums safe as: - **Map/Set keys** (stable hashCode, fast). - **`when` subjects** (cheap identity dispatch, exhaustiveness). ## Specialized collections The singleton/ordinal structure enables highly efficient `EnumMap`/`EnumSet` (from the Java standard library), backed by arrays indexed by ordinal rather than hashing. ## The cross-process caveat The "single instance" guarantee holds **within one JVM/class-loader**. If you serialize a constant and read it back in another process, you don't get the *same object* — frameworks reconnect it to the local singleton by matching **`name`** (which is exactly why you serialize by name). So reason about identity within a process, and about `name` across process boundaries.

  • Can you override equals() on an enum to change comparison?
    No. kotlin.Enum's equals and hashCode are final identity-based implementations; the compiler rejects overrides.
  • Is the singleton guarantee absolute across processes?
    Within one JVM/class-loader yes. Across processes, deserialization reconnects by name to the local constant; you don't get the same object reference.
  • Why are enums good map keys?
    Stable, identity-based hashCode and a small fixed key space; you can even use the array-backed EnumMap/EnumSet for efficiency.

Like a country's official flag: there's one canonical instance everyone points to, not a fresh copy each time you mention it.

saying these in an interview costs you the question

  • Claiming you should use === instead of == for safety (both are fine and equal)
  • Trying to override equals/hashCode on an enum
  • Thinking each `Color.RED` reference makes a new object
  • Assuming object identity survives serialization across processes
  • Not recognizing enums as safe, efficient map/set keys

context