Technology · TypeScript
TypeScript Enums
Use enums effectively for type-safe constants and fixed sets of values.
TL;DR
- 01String enums provide clarity and better debugging.
- 02Numeric enums support reverse mapping but can be confusing.
- 03Const enums inline values at compile time for smaller bundles.
String Enums
- Create enums with string values for clarity.
enum Status { Active = "active", Inactive = "inactive", Pending = "pending" } const userStatus: Status = Status.Active; - String enums are self-documenting and easier to debug.
enum Role { Admin = "admin", User = "user", Guest = "guest" } function checkRole(role: Role) { if (role === Role.Admin) { // user is admin } } - Values match database or API responses directly.
// API returns { status: "active" } — enum value matches const fromApi: Status = "active" as Status; - Use string enums in switch statements for exhaustive checks.
function describe(status: Status): string { switch (status) { case Status.Active: return "User is active"; case Status.Inactive: return "User is inactive"; case Status.Pending: return "Awaiting approval"; } } - Iterate over string enum values using Object.values.
const allStatuses = Object.values(Status); // ["active", "inactive", "pending"]
Numeric Enums
- Create enums with numeric values.
enum Direction { Up = 0, Right = 1, Down = 2, Left = 3 } const dir: Direction = Direction.Up; - Auto-increment values reduce boilerplate.
enum Level { Low, // 0 Medium, // 1 High // 2 } - Numeric enums support reverse mapping.
enum Status { Active = 1, Inactive = 2 } console.log(Status[1]); // "Active" console.log(Status.Active); // 1 - Use bit flags with numeric enums for combined permission values.
enum Permission { None = 0, Read = 1 << 0, // 1 Write = 1 << 1, // 2 Admin = 1 << 2 // 4 } const userPerms = Permission.Read | Permission.Write; // 3 - Start auto-increment from a specific number to avoid zero-falsy issues.
enum Priority { Low = 1, // starts at 1, not 0 Medium, // 2 High // 3 }
Const Enums
- Use const for enums that inline at compile time.
const enum Color { Red = "red", Green = "green", Blue = "blue" } const myColor: Color = Color.Red; // Compiles to: const myColor = "red"; - Const enums have zero runtime overhead.
const enum Direction { Up, Down, Left, Right } const dir = Direction.Up; // Compiles to: const dir = 0; — no object created - No generated enum object in the bundle.
// Regular enum: creates a runtime object // Const enum: replaces each use with its literal value const enum Size { Small = "sm", Large = "lg" } const s: Size = Size.Small; // becomes: const s = "sm"; - Only string const enums work reliably across module boundaries.
// Safe for declaration files and cross-module use const enum HttpMethod { Get = "GET", Post = "POST", Put = "PUT" } - Avoid const enums in library declaration files — they can break consumers.
// In a .d.ts file used by others, prefer a regular enum or union type export type HttpMethod = "GET" | "POST" | "PUT";
Comparing with Union Types
- Union types are simpler and don't generate code.
type Status = "active" | "inactive" | "pending"; const status: Status = "active"; - Use const with objects for enum-like behavior.
const STATUSES = { Active: "active", Inactive: "inactive" } as const; type Status = typeof STATUSES[keyof typeof STATUSES]; // Status = "active" | "inactive" - Union types are preferred for most modern TypeScript code.
// Simpler, no runtime object, serializes cleanly type Direction = "up" | "down" | "left" | "right"; - Enums are better when you need a named constant at runtime.
// Useful for logging and switch defaults enum LogLevel { Debug = "debug", Info = "info", Error = "error" } function log(level: LogLevel, msg: string) { console.log(`[${level}] ${msg}`); } - Use an as const object to get both a type and runtime values.
const Roles = { Admin: "admin", User: "user", Guest: "guest" } as const; type Role = typeof Roles[keyof typeof Roles]; // "admin" | "user" | "guest" Object.values(Roles).forEach(r => console.log(r));
Common Patterns
- Use enums for API response codes.
enum HttpStatus { Ok = 200, Created = 201, BadRequest = 400, Unauthorized = 401, NotFound = 404 } - Combine enums with type guards for safe narrowing.
function isValidStatus(value: any): value is Status { return Object.values(Status).includes(value); } - Export enums from a central file to share across modules.
// enums.ts export enum UserRole { Admin = "admin", User = "user" } // other-file.ts import { UserRole } from "./enums"; - Map enum values to display labels in a lookup object.
const statusLabels: Record<Status, string> = { [Status.Active]: "Active", [Status.Inactive]: "Inactive", [Status.Pending]: "Pending" }; console.log(statusLabels[Status.Active]); // "Active" - Use enums as discriminants in discriminated union types.
enum EventType { Click = "click", KeyPress = "keypress" } type AppEvent = | { type: EventType.Click; x: number; y: number } | { type: EventType.KeyPress; key: string };
Tip: Use string enums or union types for new code — they're clearer and don't have the reverse-mapping pitfalls of numeric enums.
Warning: Numeric enums can cause subtle bugs with reverse mapping — avoid them unless you specifically need the numeric values.