TypeScript Enums

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

TL;DR

  1. Prefer string enums over numeric ones for self-documenting, debuggable code.
  2. Numeric enums support reverse mapping but can be confusing.
  3. Const enums inline values at compile time for smaller bundles.

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; // 0
    
  • Use 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);  // 1
    
  • Use 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; // 3
    
  • Start 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 runtime
    
  • Show 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 needed
    
  • Use 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 };
    

Tips

  1. Use string enums or union types for new code — they're clearer and don't have the reverse-mapping pitfalls of numeric enums.

Warnings

  1. Numeric enums can cause subtle bugs with reverse mapping — avoid them unless you specifically need the numeric values.

FAQ