Technology · TypeScript

TypeScript Enums

Use enums effectively for type-safe constants and fixed sets of values.

TL;DR
  1. 01String enums provide clarity and better debugging.
  2. 02Numeric enums support reverse mapping but can be confusing.
  3. 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.

TypeScript Enums Best PracticesTypeScript Error Messages