CodeOath
← All posts
TypeScript75 min total · 21 parts

TypeScript Fundamentals: Types, Interfaces, Generics, and Why It Catches Bugs Before Runtime

Contents — Part 9 of 21: Enums vs. Union Literals
Part 9 of 21 · ~1 min

Enums vs. Union Literals

TypeScript's enum predates the wide adoption of literal union types and still shows up in real code, especially in codebases with a C#/Java background:

enum Direction {
  Up,
  Down,
  Left,
  Right,
}

function move(dir: Direction) { /* ... */ }
move(Direction.Up);

A numeric enum compiles to a real runtime object (unlike almost everything else in TypeScript's type system, it is not erased) and, unless given explicit values, its numbering starts at 0 and can shift unexpectedly if members are reordered. A const enum avoids emitting that object by inlining values at every use site, but has compatibility caveats with some build tools (notably isolated-file transpilers like Babel, which can't safely inline it without full type information).

type Direction2 = "up" | "down" | "left" | "right"; // no runtime object at all — pure type-erased
function move2(dir: Direction2) { /* ... */ }
move2("up");
enumUnion of string literals
Runtime footprintReal object emitted (unless const enum)None — fully erased
Refactoring safetyRenaming a member updates all usages via the compilerSame, via find-and-replace on the literal string
Serializes cleanly to JSONNumeric enums serialize as numbers, easy to misreadString literals are self-describing in JSON payloads
Common modern preferenceLess common in new codeThe more idiomatic modern default for most codebases

Most current style guides (including TypeScript's own team, in various public statements) lean toward plain union literal types for new code, reserving enum for cases where you specifically want the extra runtime object (e.g. iterating over all values).