TypeScript Enums
Use enums effectively for type-safe constants and fixed sets of values.
TL;DR
- 01Prefer string enums over numeric ones for self-documenting, debuggable code.
- 02Numeric enums support reverse mapping but can be confusing.
- 03Const enums inline values at compile time for smaller bundles.
Tips
- 01Use string enums or union types for new code — they're clearer and don't have the reverse-mapping pitfalls of numeric enums.
Warnings
- 01Numeric enums can cause subtle bugs with reverse mapping — avoid them unless you specifically need the numeric values.
String Enums
Create a string enum to assign readable values to each member.
enum Status { Active = "active", Inactive = "inactive", Pending = "pending" } const userStatus: Status = Status.Active; // "active"Use string enums in switch statements for exhaustive, readable 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"; } }Match API or database string values directly using string enum members.
// API returns { status: "active" } — enum value matches exactly const fromApi: Status = "active" as Status;Iterate over all string enum values using Object.values.
const allStatuses = Object.values(Status); // ["active", "inactive", "pending"]Use a string enum to keep role names self-documenting in conditions.
enum Role { Admin = "admin", User = "user", Guest = "guest" } function checkRole(role: Role) { if (role === Role.Admin) console.log("is admin"); // "admin" in logs }
Numeric Enums
Create a numeric enum — members auto-increment from zero by default.
enum Level { Low, // 0 Medium, // 1 High // 2 }Set explicit values to control what numbers each member maps to.
enum Direction { Up = 0, Right = 1, Down = 2, Left = 3 } const dir: Direction = Direction.Up; // 0Use reverse mapping to look up a member name from its numeric value.
enum Status { Active = 1, Inactive = 2 } console.log(Status[1]); // "Active" console.log(Status.Active); // 1Use bit flags with left-shift to combine permissions in a single number.
enum Permission { None = 0, Read = 1 << 0, // 1 Write = 1 << 1, // 2 Admin = 1 << 2 // 4 } const userPerms = Permission.Read | Permission.Write; // 3Start auto-increment from 1 to avoid zero-falsy truthiness bugs.
enum Priority { Low = 1, // starts at 1, not 0 Medium, // 2 High // 3 }
Const Enums
Declare a const enum to inline every member as a literal at compile time.
const enum Color { Red = "red", Green = "green", Blue = "blue" } const myColor: Color = Color.Red; // Compiles to: const myColor = "red";Verify zero runtime overhead — no JS object is generated in the bundle.
const enum Direction { Up, Down, Left, Right } const dir = Direction.Up; // Compiles to: const dir = 0; — no Direction object exists at runtimeShow the size difference between regular and const enums.
// Regular enum: creates a runtime object (adds ~200 bytes) // Const enum: replaces each use with its literal value (adds 0 bytes) const enum Size { Small = "sm", Large = "lg" } const s: Size = Size.Small; // becomes: const s = "sm";Use string const enums safely when sharing across module boundaries.
// Safe for cross-module use with string values const enum HttpMethod { Get = "GET", Post = "POST", Put = "PUT" }Avoid const enums in .d.ts library files — consumers cannot inline them.
// In a .d.ts file, prefer a union type or regular enum export type HttpMethod = "GET" | "POST" | "PUT";
Comparing with Union Types
Use union types for simple value sets — they need no runtime code.
type Status = "active" | "inactive" | "pending"; const status: Status = "active"; // direct string, no enum import neededUse an as const object to get both runtime values and a derived type.
const STATUSES = { Active: "active", Inactive: "inactive" } as const; type Status = typeof STATUSES[keyof typeof STATUSES]; // Status = "active" | "inactive"Prefer union types when you need to serialize values to JSON cleanly.
// Simpler, no runtime object, serializes as a plain string type Direction = "up" | "down" | "left" | "right";Use enums when you need a named constant group importable as one symbol.
// Useful when the group name matters for readability and logging enum LogLevel { Debug = "debug", Info = "info", Error = "error" } function log(level: LogLevel, msg: string) { console.log(`[${level}] ${msg}`); }Combine an as const object with Object.values to iterate like an enum.
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 a numeric enum for HTTP status codes to keep values readable.
enum HttpStatus { Ok = 200, Created = 201, BadRequest = 400, Unauthorized = 401, NotFound = 404 }Write a type guard to check if a runtime value is a valid enum member.
function isValidStatus(value: unknown): value is Status { return Object.values(Status).includes(value as Status); }Export all enums from a central file to share them across modules.
// enums.ts export enum UserRole { Admin = "admin", User = "user" } // other-file.ts import { UserRole } from "./enums";Use Record with an enum type to create exhaustive display label maps.
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 for events.
enum EventType { Click = "click", KeyPress = "keypress" } type AppEvent = | { type: EventType.Click; x: number; y: number } | { type: EventType.KeyPress; key: string };
FAQ
Prefer union types like type Direction = 'north' | 'south' for simple sets of values — they require no runtime code and are easier to extend. Use string enums when you want a named, iterable group of constants that appear as a single importable symbol across your codebase.
Numeric enums generate reverse mappings at runtime, so Object.keys(MyEnum) returns both the names and their numeric values (e.g., ['Up', 'Down', '0', '1']). Filter with isNaN or switch to string enums to avoid this pitfall.
A const enum is erased entirely at compile time — all usages are replaced with their literal values, producing no JavaScript object. Use it when bundle size matters and you don't need to iterate over or import the enum at runtime, but note it's incompatible with isolatedModules mode.
No — enums only support constant or computed member values, not methods. If you need behavior attached to enum values, use a plain object with as const or a class with static members instead.
Cast the enum to Record<string, string> and use Object.values: Object.values(MyEnum).includes(value as MyEnum). TypeScript won't narrow this automatically, so you'll need a type guard function that returns value is MyEnum for safe use downstream.