Enums
An enum defines a named set of constant values. Unlike most TypeScript constructs, an enum is one of the rare features that is not fully erased — it produces real runtime code (full detail in "Under the Hood").
🎯 What you'll learn
- What an Enum Is
- Numeric Enums — Auto-Incrementing
- String Enums
- Reverse Mapping — Numeric Enums Only
const enum— Fully Inlined, No Runtime Object
1What an Enum Is
An enum defines a named set of constant values. Unlike most TypeScript constructs, an enum is one of the rare features that is not fully erased — it produces real runtime code (full detail in "Under the Hood").
enum Direction {
Up,
Down,
Left,
Right,
}
let move: Direction = Direction.Up;
console.log(move); // 02Numeric Enums — Auto-Incrementing
enum Status {
Pending, // 0
Active, // 1
Closed, // 2
}
enum HttpCode {
OK = 200,
NotFound = 404,
ServerError = 500,
}
enum Mixed {
A, // 0
B = 10, // explicitly 10
C, // 11 — continues incrementing from the previous value
}By default, numeric enum members start at 0 and increment by 1; you can override any member's value explicitly, and subsequent unset members continue incrementing from there.
3String Enums
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
console.log(Direction.Up); // "UP" — readable at runtime (e.g. in logs, network payloads)String enums require every member to have an explicit value (no auto-increment) and don't support reverse mapping (section 4) — but they produce far more debuggable runtime values than numeric enums' plain integers.
4Reverse Mapping — Numeric Enums Only
enum Status { Pending, Active, Closed }
console.log(Status.Active); // 1 — forward mapping: name → value
console.log(Status[1]); // "Active" — reverse mapping: value → nameNumeric enums get an automatic reverse lookup; string enums do not (a string enum member's key literally collides with nothing reversible the way sequential integers do).
5const enum — Fully Inlined, No Runtime Object
const enum Direction {
Up,
Down,
}
let move = Direction.Up; // compiles to: let move = 0; — NO Direction object exists at runtime at allA const enum is a compiler-only construct: every reference to a member is replaced with its literal value directly at compile time, and no runtime object is generated whatsoever — the fastest and smallest option, at the cost of losing reverse mapping and runtime introspection entirely.
6Enums vs Union of Literals (Preview)
enum StatusEnum { Pending, Active, Closed }
type StatusUnion = "pending" | "active" | "closed";Both express a fixed set of allowed values. A union of string literals has zero runtime footprint (fully erased, per the Union Types topic) and is often preferred in modern TypeScript codebases specifically to avoid enums' non-standard runtime behavior — full comparison belongs in the Union Types topic, but it's worth knowing this tradeoff exists before reaching for enum by default.
Under the HoodWhat an Enum Actually Compiles To▾
A regular (non-const) enum compiles to an IIFE building a real object
enum Status { Pending, Active, Closed }compiles to roughly:
var Status;
(function (Status) {
Status[(Status["Pending"] = 0)] = "Pending";
Status[(Status["Active"] = 1)] = "Active";
Status[(Status["Closed"] = 2)] = "Closed";
})(Status || (Status = {}));Reading this carefully: each line does TWO assignments — Status["Pending"] = 0 (the forward mapping) and then uses that same assignment's result (0) as the key for Status[0] = "Pending" (the reverse mapping) — this single dense line is exactly how numeric enums end up supporting both Status.Pending and Status[0] lookups. This is real, executed JavaScript — a genuine object gets built at module load time — which is exactly why enum is one of the few TypeScript features that isn't purely a compile-time-only construct.
String enums skip the reverse-mapping half
enum Direction { Up = "UP" }compiles to:
var Direction;
(function (Direction) {
Direction["Up"] = "UP";
})(Direction || (Direction = {}));Only the forward assignment happens — there's no Direction["UP"] = "Up" line — which is the concrete compiled reason string enums have no reverse mapping (section 4): the compiler simply never emits the code that would create one.
const enum never creates the object at all
For a const enum, the compiler doesn't emit any IIFE or object — every usage site is scanned and the member reference is replaced with its literal value directly, inline, at every call site. This is why const enum is sometimes disallowed in certain build setups (e.g. Babel-based toolchains, or isolatedModules mode): those tools compile files independently without full cross-file type information, and can't perform this inlining substitution correctly without seeing the enum's actual declaration at every use site.
⚠️ Common Pitfalls
- Reordering numeric enum members — auto-incremented values shift for everything after the change, silently breaking any previously-serialized/stored numeric values that assumed the old ordering.
- Expecting string enums to support reverse mapping — they don't; only numeric enums get the value→name lookup, per section 4/7.
- Using
const enumacross module boundaries with incompatible build tooling — inlining requires full type information at compile time; some transpilers/bundlers can't do this safely and will error or produce incorrect output. - Reaching for
enumreflexively instead of considering a literal union — enums carry real runtime cost and non-standard behavior a literal union type doesn't; many modern style guides prefer literal unions for this reason (see section 6).
📋 Quick Reference
enum X { A, B } → real runtime object, forward + reverse mapping
enum X { A = "a" } → string enum — forward mapping only, must set every value explicitly
const enum X { A, B } → fully inlined, NO runtime object at all🎤 Interview Q&A
Q1What kinds of enums exist in TypeScript?core▾
Numeric enums (auto-incremented, with reverse mapping), string enums (explicit values, no reverse mapping), const enum (inlined at compile time), and the alternative: union of string literals or as const objects.
- Do enums exist at runtime? Regular enums emit JS objects; const enums don't.
Q2Why do many teams avoid enums?core▾
They generate runtime code (not just types), numeric enums accept any number, const enums break with isolated module compilers, and they don't align with the 'types erase' model (erasableSyntaxOnly). Union types or as const objects are simpler.
- as const pattern?
const Status = { Active: 'active' } as const; type Status = typeof Status[keyof typeof Status].
Q3When are enums reasonable?senior▾
When you want a named runtime object for values and iteration, in codebases already using them, or for interop with backends using the same names. String enums are safer than numeric ones.
- Numeric enum pitfall?
let s: Status = 42compiles.
Q4How do you iterate enum values?core▾
Object.values(StringEnum); numeric enums include reverse mappings, so filter numbers. With as const objects, Object.values works directly.
- Type-safe keys?
keyof typeof Enum.
Q5How do union types compare in tooling?core▾
Literal unions give autocompletion, exhaustive checks and zero runtime cost, and serialize naturally to JSON.
- Downside? No runtime list of values unless you define one.
🛠️ Practice exercises
Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.