TypeScript Enums

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

TL;DR

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

Tips

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

Warnings

  1. 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; // 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 };
    

FAQ