|$ curl https://forge-ai.dev/api/markdown?path=docs/typescript/enums
$cat docs/typescript-—-enums.md
updated Recently·8 min read·published

TypeScript — Enums

TypeScriptBeginner
Introduction

Enums (enumerations) are a TypeScript feature that doesn't exist in JavaScript. They let you define a set of named constants — either numeric or string — that represent a fixed collection of values. Enums are useful when you have a small, known set of options like status codes, directions, or error types.

TypeScript has numeric enums, string enums, heterogeneous enums, computed enums, and const enums. Each has trade-offs in terms of runtime behavior, bundle size, and type safety.

📝

note

Enums are not a pure TypeScript type-level construct — they generate real JavaScript objects at runtime. This distinguishes them from most other TypeScript features which are erased during compilation.
Numeric Enums

Numeric enums assign auto-incrementing numbers to members. The first member defaults to 0, and each subsequent member increments by 1. You can also set explicit values.

numeric_enums.ts
TypeScript
1// Basic numeric enum — auto-increments from 0
2enum Direction {
3 Up, // 0
4 Down, // 1
5 Left, // 2
6 Right, // 3
7}
8
9console.log(Direction.Up); // 0
10console.log(Direction[0]); // "Up" (reverse mapping)
11
12// Explicit values
13enum HttpStatus {
14 OK = 200,
15 Created = 201,
16 BadRequest = 400,
17 Unauthorized = 401,
18 NotFound = 404,
19 ServerError = 500,
20}
21
22function handleStatus(code: HttpStatus) {
23 switch (code) {
24 case HttpStatus.OK:
25 return "success";
26 case HttpStatus.NotFound:
27 return "not found";
28 default:
29 return "unknown";
30 }
31}
32
33// Partial explicit — rest continue incrementing
34enum Color {
35 Red = 1,
36 Green, // 2
37 Blue, // 3
38}
39
40// Numeric enums accept any number (a quirk)
41let c: Color = Color.Red;
42c = 42; // OK at compile time — no error!

warning

Numeric enums accept any number value, not just the defined members. This is a design quirk — let c: Color = 42 compiles without error. Use string enums or union types to avoid this.
String Enums

String enums require explicit values for every member. They are more predictable than numeric enums — each member is a string literal, making debugging easier and serialization more reliable.

string_enums.ts
TypeScript
1// String enum — every member must have a string value
2enum Status {
3 Active = "ACTIVE",
4 Inactive = "INACTIVE",
5 Pending = "PENDING",
6 Deleted = "DELETED",
7}
8
9let s: Status = Status.Active;
10console.log(s); // "ACTIVE" (not a number)
11
12// String enums don't accept arbitrary strings
13// let bad: Status = "ACTIVE"; // Error: "ACTIVE" is not assignable to Status
14// This is the key difference from numeric enums!
15
16// Practical: API status
17enum ApiStatus {
18 Idle = "idle",
19 Loading = "loading",
20 Success = "success",
21 Error = "error",
22}
23
24// Practical: log levels
25enum LogLevel {
26 Debug = "DEBUG",
27 Info = "INFO",
28 Warn = "WARN",
29 Error = "ERROR",
30}
31
32function log(level: LogLevel, message: string) {
33 console.log(`[${level}] ${message}`);
34}
35
36log(LogLevel.Info, "Server started"); // [INFO] Server started
37
38// String enums in discriminated unions
39type ApiResponse =
40 | { status: ApiStatus.Success; data: unknown }
41 | { status: ApiStatus.Error; message: string };
Heterogeneous & Computed Enums

Heterogeneous enums mix string and numeric members. Computed enums use expressions as values. Both exist but are rarely needed — prefer string enums for clarity.

heterogeneous.ts
TypeScript
1// Heterogeneous enum — mixed string and number (rarely useful)
2enum Mixed {
3 No = 0,
4 Yes = "YES",
5}
6
7// Computed enum — values can be expressions
8enum FileAccess {
9 None = 0,
10 Read = 1 << 1, // 2
11 Write = 1 << 2, // 4
12 ReadWrite = Read | Write, // 6
13}
14
15console.log(FileAccess.ReadWrite); // 6
16
17// Computed from function
18function computeValue(): number {
19 return Math.random() * 100;
20}
21
22enum Computed {
23 A = computeValue(), // computed at runtime
24 B = A * 2, // depends on A
25}
26
27// Enum with computed values must come after all constant members
28enum Order {
29 First = "first", // constant
30 Second = "second", // constant
31 Third = "third", // constant
32 Fourth = Second.toUpperCase(), // computed — must be last
33}

best practice

Avoid heterogeneous enums — mixing types in a single enum is confusing. Use string enums for human-readable values and numeric enums only when you need bitwise operations or interop with numeric APIs.
Const Enums (Performance)

Const enums are fully inlined at compile time — no JavaScript object is generated. This saves bundle size but comes with limitations: they can't be used dynamically and don't support reverse mappings.

const_enum.ts
TypeScript
1// Const enum — fully inlined, no runtime object
2const enum Color {
3 Red = "RED",
4 Green = "GREEN",
5 Blue = "BLUE",
6}
7
8// Compiles to (no Color object in output):
9// console.log("RED");
10
11let c: Color = Color.Red;
12console.log(c); // "RED" — value is inlined
13
14// Regular enum generates runtime code:
15// enum Color { Red = "RED", Green = "GREEN", Blue = "BLUE" }
16// → creates an object at runtime
17
18// Const enum limitations:
19// 1. No reverse mapping
20// Color["RED"]; // Error
21
22// 2. Can't use as values dynamically
23// let x = Color; // Error
24
25// 3. Can't use with computed values
26// const enum Bad {
27// A = Math.random(), // Error: computed values not allowed
28// }
29
30// 4. Can't use with object values
31// const enum WithObj {
32// A = { x: 1 }, // Error
33// }
34
35// When to use const enum:
36// - Internal constants in performance-critical code
37// - When bundle size matters
38// - When you only need compile-time access
39
40// When to use regular enum:
41// - When you need runtime access
42// - When you need reverse mapping
43// - When values come from external sources

info

In most projects, const enumsavings are negligible. Use regular string enums unless you have a proven performance bottleneck. Modern bundlers (esbuild, SWC) don't inline const enums anyway.
Reverse Mappings

Numeric enums support reverse mapping — looking up the name from a value. String enums and const enums do not. This is primarily useful for debugging and logging.

reverse_mapping.ts
TypeScript
1// Numeric enum — bidirectional mapping
2enum Direction {
3 Up = "UP",
4 Down = "DOWN",
5 Left = "LEFT",
6 Right = "RIGHT",
7}
8
9// Wait — this is actually a string enum. Let's use numeric:
10enum Season {
11 Spring, // 0
12 Summer, // 1
13 Autumn, // 2
14 Winter, // 3
15}
16
17// Forward: name → value
18console.log(Season.Spring); // 0
19console.log(Season.Winter); // 3
20
21// Reverse: value → name (only for numeric enums)
22console.log(Season[0]); // "Spring"
23console.log(Season[1]); // "Summer"
24
25// Practical: logging with enum names
26function logSeason(s: Season) {
27 console.log(`Season: ${Season[s]}`); // "Season: Spring"
28}
29
30logSeason(Season.Spring); // "Season: Spring"
31
32// Reverse mapping doesn't work for string enums
33enum Color {
34 Red = "red",
35 Blue = "blue",
36}
37
38// Color["red"]; // Error: Property 'red' does not exist
39
40// Workaround for string enums: create a reverse map manually
41const colorMap = Object.values(Color) as string[];
42function getColorName(value: string): string | undefined {
43 return colorMap.find(v => v === value);
44}
Enums at Runtime

Enums generate real JavaScript objects at runtime. Understanding this helps with serialization, interop, and avoiding surprises.

enums_runtime.ts
TypeScript
1// What TypeScript compiles enums to:
2enum Direction {
3 Up = "UP",
4 Down = "DOWN",
5}
6
7// Compiles to approximately:
8// var Direction;
9// (function (Direction) {
10// Direction["Up"] = "UP";
11// Direction["Down"] = "DOWN";
12// })(Direction || (Direction = {}));
13
14// Runtime: Direction is a real object
15console.log(typeof Direction); // "object"
16console.log(Object.keys(Direction)); // ["Up", "Down"]
17console.log(Direction); // { Up: "UP", Down: "DOWN" }
18
19// Enum values in JSON serialization
20enum Status {
21 Active = "ACTIVE",
22 Inactive = "INACTIVE",
23}
24
25let user = { name: "Alice", status: Status.Active };
26let json = JSON.stringify(user);
27// '{"name":"Alice","status":"ACTIVE"}' — serializes the VALUE, not the name
28
29// Enum from JSON (deserialize)
30let parsed = JSON.parse(json);
31let status = parsed.status as Status; // need assertion
32
33// Enum comparison
34function check(value: Status) {
35 if (value === Status.Active) {
36 return true;
37 }
38 return false;
39}
40
41// Enum in switch
42function handle(status: Status) {
43 switch (status) {
44 case Status.Active:
45 return "active";
46 case Status.Inactive:
47 return "inactive";
48 }
49}
When to Use Enums vs Union Types

TypeScript's literal union types can replace enums in most cases. The choice depends on whether you need runtime objects, reverse mapping, or are working with an existing API that uses enums.

enums_vs_unions.ts
TypeScript
1// UNION TYPES — no runtime object generated
2type Direction = "north" | "south" | "east" | "west";
3
4function move(dir: Direction) {
5 // value is just a string at runtime
6}
7
8move("north"); // OK
9
10// ENUMS — runtime object generated
11enum DirectionEnum {
12 North = "north",
13 South = "south",
14 East = "east",
15 West = "west",
16}
17
18function moveEnum(dir: DirectionEnum) {
19 // DirectionEnum is a real object at runtime
20}
21
22moveEnum(DirectionEnum.North); // OK
23
24// COMPARISON:
25// Union types:
26// ✓ No runtime overhead
27// ✓ Tree-shakeable
28// ✓ Simple, no special syntax
29// ✗ No runtime object for iteration
30// ✗ No reverse mapping
31
32// Enums:
33// ✓ Runtime object (can iterate, access keys)
34// ✓ Reverse mapping (numeric)
35// ✓ Familiar pattern from other languages
36// ✗ Generates runtime code
37// ✗ Not tree-shakeable
38// ✗ Numeric enums accept arbitrary values
39
40// RECOMMENDATION:
41// 1. Start with union types (simpler, no runtime cost)
42// 2. Use enums when you need runtime access or iteration
43// 3. Use string enums over numeric when you do use enums
44// 4. Use const enum only for performance-critical code
45
46// Hybrid: enum + union
47enum Color {
48 Red = "red",
49 Green = "green",
50 Blue = "blue",
51}
52
53// Get union type from enum
54type ColorValue = `${Color}`; // "red" | "green" | "blue"
55
56// Use the union type for type-safe functions
57function paint(c: ColorValue) {
58 console.log(`Painting ${c}`);
59}
60
61paint("red"); // OK
62paint(Color.Red); // OK — enum value matches union

best practice

For most TypeScript projects, prefer union types over enums. They're simpler, have no runtime cost, and align with TypeScript's structural type system. Use enums when you need a runtime representation of your constants (iteration, serialization, API interop).
$Blueprint — Engineering Documentation·Section ID: TS-ENUMS·Revision: 1.0