skip to content

Explain the performance/allocation difference between iterating `MyEnum.values()` and `MyEnum.entries`, and when it actually matters.

level: middleimportance: should knowfreq 55%

answer

  1. values() = defensive copy per call → GC pressure
  2. entries = cached single instance → zero alloc
  3. Matters in hot loops / per-request, not cold paths
  4. Mutable array forces copy; immutable list allows caching
  5. enumValues<T>() also allocates; enumEntries<T>() caches

basics

~10 s

values() builds a new array every time you call it, which creates garbage. entries reuses one shared list, so it allocates nothing. It matters in hot loops or tight memory situations.

solid answer

~40 s

`values()` returns `Array<E>`. Arrays are mutable, so the compiler-generated `values()` must clone the backing array on every invocation to stop callers from mutating shared state — that's one allocation per call plus GC pressure. `entries` is an immutable `EnumEntries<E>` (a `List<E>`); because it can't be mutated, the runtime hands back a single cached instance, so repeated access allocates nothing. The difference is invisible for a one-off call but real in hot paths: a `when` over enum values inside a tight loop, validation in request handlers, or serialization code that touches `values()` per element. Micro-benchmarks aside, the safe default is `entries`. Note `enumValues<E>()` (the inline reified helper) still delegates to `values()`-style array semantics, so it allocates too.

code

kotlin · 7 lines
kotlin
enum class Level { LOW, MEDIUM, HIGH }

// Allocation-free: entries cached once
val levelsByName = Level.entries.associateBy { it.name }

fun parse(name: String): Level =
    levelsByName[name] ?: error("unknown $name")

go deeper

for a junior

Recognizes values() makes a copy and entries does not, even without deep GC reasoning.

for a middle

Explains defensive-copy-per-call vs cached instance and identifies hot loops as where it matters.

for a senior

Ties immutability to safe caching, mentions enumValues/enumEntries, and avoids over-optimizing cold paths.

for a principal

Reasons about GC/throughput tradeoffs, multiplatform backends, and sets a team default with guidance on the rare array-needed exceptions.

## The core mechanism The compiler synthesizes `values()` to return `Array<E>`. An **array** is a mutable, fixed-size container; element slots can be reassigned. If `values()` returned a shared array, any caller could write `arr[0] = SomethingElse` and corrupt every future caller. To stay safe, `values()` returns a **fresh defensive copy each call** → **one allocation per call**. `entries` returns `EnumEntries<E>`, which **implements `List<E>`** and is **immutable** (no `add`/`set`). Immutability removes the corruption risk, so the runtime returns a **single cached instance** for the lifetime of the program → **zero allocations** on repeated access. ## Why "allocation" is the keyword Each `values()` call produces a short-lived object (the array). In a hot loop this means: - More work for the **garbage collector** (the JVM subsystem that reclaims unused memory). - Cache/throughput noise from churning short-lived objects. ```kotlin enum class Status { OK, WARN, ERROR } // BAD in a hot path: allocates a new Array on every iteration fun countBad(rows: List<Row>): Int = rows.count { r -> Status.values().any { it == r.status } } // GOOD: entries is cached, no per-iteration allocation fun countGood(rows: List<Row>): Int = rows.count { r -> Status.entries.any { it == r.status } } ``` ## When it actually matters - **Hot loops / per-request work**: validators, parsers, serializers that touch the enum set repeatedly. - **Memory-constrained** targets (Android, embedded). - It is **negligible** for startup-time or one-shot calls — don't over-optimize cold paths. ## Gotchas - Even outside loops, prefer `entries` because it's idiomatic and List-typed (no array-to-list conversion). - `enumValues<T>()` (inline `reified`) is the generic analogue of `values()` and **also allocates** an array; there is no generic `entries` equivalent prior to it being added — use `enumEntries<T>()` (Kotlin 1.9+) for the cached, allocation-free generic form. ## Takeaway Default to `entries`. Use `values()` only when an `Array` is genuinely required (e.g. interop). The win is correctness-by-immutability first, allocation savings second.

  • Does `enumValues<T>()` allocate like `values()`?
    Yes — it has array semantics and allocates per call. Use `enumEntries<T>()` (Kotlin 1.9+) for the cached, allocation-free generic version.
  • Is the allocation difference ever worth ignoring?
    Yes, on cold/one-shot paths it's negligible; readability and correctness drive the choice there, and both favor `entries` anyway.

saying these in an interview costs you the question

  • Claiming `values()` is cached / allocation-free.
  • Saying `entries` allocates a copy on each access.
  • Over-claiming huge perf wins on cold paths.
  • Not knowing why immutability enables caching.
  • Confusing GC pressure with a memory leak (it's transient garbage, not a leak).

context