enum declares a set of named constants — numeric (auto-incrementing from 0 by default) or string (each member needs an explicit value). Unlike most TypeScript constructs, a regular enum is not erased at compile time: it generates an actual JavaScript object at runtime, unless declared const enum, which inlines the values with zero runtime object. This is one of the few areas where the official TypeScript Handbook itself documents genuine drawbacks — including that a numeric enum's type unsafely accepts any number — and now points most new code toward literal-type unions instead.
Numeric and string enums
Numeric enums support reverse mapping (going from the value back to the member name); string enums do not, since that mapping would be ambiguous with string values.
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
console.log(Direction.Up); // 0
console.log(Direction[0]); // "Up" — reverse mapping, numeric enums only
enum Status {
Pending = "PENDING",
Active = "ACTIVE",
}
console.log(Status.Active); // "ACTIVE"The real type-safety gap in numeric enums
This is documented in the official handbook itself as a genuine drawback — not a myth or an exaggeration — because bitmask-style enum usage legitimately relies on arbitrary numeric values, so TypeScript can't universally reject this without breaking that pattern.
enum Direction { Up, Down, Left, Right }
function move(direction: Direction) { /* ... */ }
move(Direction.Up); // fine
move(99); // ALSO type-checks — no error, even though 99 isn't a real memberWhy the handbook now favors union types
A union of string literals is completely erased at compile time (zero runtime bytes, unlike a regular enum's generated object), doesn't have this type-safety gap, and needs no import to reference — which is why the official TypeScript documentation itself now points most new code toward this pattern instead of enum, reserving enums mainly for codebases that already use them extensively or specifically need reverse mapping.
type Direction = "up" | "down" | "left" | "right"; // the now-preferred alternative
function move(direction: Direction) { /* ... */ }
move("up"); // fine
// move("diagonal"); // COMPILE ERROR — this one IS properly rejected