Type-Safe Enums — Optimization Drills¶
Category: Resource & Type-Safety Patterns — squeeze correctness and performance out of enums: the right collection, no wasted allocations, no fragile coupling.
10 inefficient implementations + benchmarks + optimizations.
Apple M2 Pro, single thread. Indicative numbers.
Optimization 1: EnumSet Instead of HashSet<Enum>¶
Slow¶
Set<Perm> perms = new HashSet<>();
perms.add(Perm.READ);
perms.contains(Perm.WRITE); // hash + bucket lookup, boxing
Optimized¶
EnumSet<Perm> perms = EnumSet.of(Perm.READ);
perms.contains(Perm.WRITE); // single bit test in a long
Benchmark¶
EnumSet is a bit vector — ~8× faster and allocation-free.
Optimization 2: EnumMap Instead of HashMap<Enum,V>¶
Slow¶
Optimized¶
EnumMap<Status, Handler> handlers = new EnumMap<>(Status.class);
handlers.get(status); // array index by ordinal
Benchmark¶
Array-indexed by ordinal: no hashing, perfect locality, iteration in declaration order.
Optimization 3: Cache values()¶
Slow¶
for (int i = 0; i < 1_000_000; i++) {
Status s = Status.values()[i % Status.values().length]; // clones twice per iter
}
values() returns a defensive clone every call.
Optimized¶
private static final Status[] VALUES = Status.values(); // clone once
for (int i = 0; i < 1_000_000; i++) {
Status s = VALUES[i % VALUES.length];
}
Benchmark¶
Cache values() once on hot paths.
Optimization 4: switch Instead of if-chain on Enums¶
Slow¶
if (s == Status.PENDING) { ... }
else if (s == Status.PAID) { ... }
else if (s == Status.SHIPPED) { ... } // linear comparisons
Optimized¶
switch (s) { // compiles to tableswitch (O(1) jump)
case PENDING -> { ... }
case PAID -> { ... }
case SHIPPED -> { ... }
}
The compiler turns enum switch into a tableswitch on the ordinal — constant-time dispatch and it enables exhaustiveness checking.
Optimization 5: Move Lookup to a Static Map (not a linear scan)¶
Slow¶
public static Status fromCode(String code) {
for (Status s : values()) // O(n) scan, clones values() each call
if (s.code().equals(code)) return s;
throw new IllegalArgumentException(code);
}
Optimized¶
private static final Map<String, Status> BY_CODE = new HashMap<>();
static { for (Status s : values()) BY_CODE.put(s.code(), s); }
public static Status fromCode(String code) {
Status s = BY_CODE.get(code);
if (s == null) throw new IllegalArgumentException(code);
return s;
}
O(1) lookup, built once at class init.
Optimization 6: stringer Instead of a switch String() in Go¶
Slow / allocating¶
func (s Status) String() string {
return map[Status]string{Pending: "pending", Paid: "paid"}[s] // builds a map each call!
}
Optimized — stringer-generated¶
//go:generate stringer -type=Status
// generated: single backing string + offset table, zero allocation
Benchmark¶
The generated version is a substring of one constant string — no allocation.
Optimization 7: Avoid Re-parsing Strings in Hot Loops¶
Slow¶
Optimized — parse once at ingestion¶
# At load time:
typed = [(r, Status(r["status"])) for r in rows]
# Hot loop works on typed values:
for r, status in typed:
if status is Status.PAID:
...
Parse strings into enums once at the boundary; the hot loop compares singletons (is), which is a pointer check.
Optimization 8: Bitset Flags Instead of a Set of Booleans¶
Slow / bulky¶
Optimized¶
EnumSet<Perm> perms = EnumSet.of(Perm.READ, Perm.WRITE);
boolean canBoth = perms.containsAll(EnumSet.of(Perm.READ, Perm.WRITE));
One long holds all flags; set algebra (containsAll, removeAll) is a single bitwise op over the whole set.
Optimization 9: Don't Allocate in Per-Constant Behavior¶
Slow¶
enum State {
PENDING { Set<State> next() { return new HashSet<>(List.of(PAID)); } }; // allocates each call
abstract Set<State> next();
}
Optimized¶
enum State {
PENDING { Set<State> next() { return EnumSet.of(PAID); } }; // cheap, or...
abstract Set<State> next();
}
// Better: precompute once.
private static final Map<State, Set<State>> NEXT = new EnumMap<>(State.class);
Precompute transition sets into a static final EnumMap so per-call behavior reads a constant, not a fresh allocation.
Optimization 10: Use IntEnum/StrEnum Only When Interop Pays¶
Slow boundary churn¶
class Status(Enum):
PAID = "paid"
json.dumps({"status": Status.PAID.value}) # manual .value everywhere
Optimized for serialization-heavy paths¶
from enum import StrEnum
class Status(StrEnum):
PAID = "paid"
json.dumps({"status": Status.PAID}) # StrEnum IS a str — serializes directly
Tradeoff¶
StrEnum/IntEnum remove boundary boilerplate but weaken the type boundary (members compare equal to raw str/int). Use them only where serialization interop is the dominant concern; prefer plain Enum for strict domain logic.
Optimization Tips¶
How to find enum bottlenecks¶
- Profile —
async-profiler/py-spy/pprofwill showvalues()clones and per-call map builds if they're hot. - Check allocations —
values(),map[...]{}literals, andnew HashSetin per-constant methods are common allocators. - Prefer
EnumSet/EnumMapthe moment an enum is a collection key/element.
Optimization checklist¶
-
EnumSetoverHashSet<Enum>. -
EnumMapoverHashMap<Enum,V>. - Cache
values()on hot paths. -
switch(tableswitch) overif-chains. - Static map for code→enum lookup.
-
stringerover hand-rolled allocatingString()in Go. - Parse strings to enums once, at the boundary.
- Precompute per-constant data into a
static final EnumMap.
Anti-optimizations¶
- ❌ Persisting ordinals to "save space". Fragile; corrupts on reorder.
- ❌
IntEnum/StrEnumfor everything to dodge.value— weakens the type boundary. - ❌ A
default:to "skip a branch". Disables exhaustiveness for a nanosecond saved. - ❌ Premature bitset micro-optimization over
EnumSet, which is already a bitset.
Summary¶
Enum optimization is mostly picking the enum-aware collection (EnumSet/EnumMap are bit vectors / ordinal arrays), avoiding repeated values() clones and per-call allocations, and parsing strings to enums once at the boundary. The type-safety win is free; the performance win comes from using the structures the language already optimized for enums — without trading away the correctness guarantees.
← Find-Bug · Resource & Type-Safety · Roadmap
Type-Safe Enums roadmap complete. All 8 files: junior · middle · senior · professional · interview · tasks · find-bug · optimize.
Next: Sentinel & Special Values.
In this topic