Technology · TypeScript

TypeScript Enums Best Practices

Use enums effectively for type-safe constants and avoid common pitfalls.

TL;DR
  1. 01Use string enums for better debugging and serialization.
  2. 02Use const enums to avoid runtime overhead.
  3. 03Prefer union types over enums for simpler type definitions.

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.
    // Logs "active" instead of "0" — readable in console and logs
    console.log(Status.Active); // "active"
  • Values are explicit and match database/API values.
    enum Role {
      Admin = "admin",
      User = "user",
      Guest = "guest"
    }
    
    function checkRole(role: Role) {
      if (role === Role.Admin) {
        // user is admin
      }
    }
  • Use string enums in switch statements for exhaustive type checking.
    function describe(s: Status): string {
      switch (s) {
        case Status.Active:   return "Active";
        case Status.Inactive: return "Inactive";
        case Status.Pending:  return "Pending";
      }
    }
  • Iterate string enum values safely with Object.values.
    const values = Object.values(Status);
    // ["active", "inactive", "pending"]

Numeric Enums

  • Create enums with numeric values for flexibility.
    enum Direction {
      Up = 0,
      Right = 1,
      Down = 2,
      Left = 3
    }
    
    const dir: Direction = Direction.Up;
  • Numeric enums support reverse mapping.
    enum Status {
      Active = 1,
      Inactive = 2
    }
    
    console.log(Status[1]);  // "Active"
    console.log(Status.Active);  // 1
  • Auto-incrementing values reduce boilerplate.
    enum Level {
      Low,      // 0
      Medium,   // 1
      High      // 2
    }
  • Start numeric enums at 1 to avoid falsy zero bugs.
    enum Priority {
      Low = 1,  // avoids if (priority) being false for Low
      Medium,   // 2
      High      // 3
    }
  • Use bit flags with numeric enums for combined permissions.
    enum Permission {
      None  = 0,
      Read  = 1 << 0, // 1
      Write = 1 << 1, // 2
      Admin = 1 << 2  // 4
    }
    
    const canEdit = Permission.Read | Permission.Write; // 3

Const Enums

  • Use const for enums that are inlined at compile time.
    const enum Color {
      Red = "red",
      Green = "green",
      Blue = "blue"
    }
    
    const myColor: Color = Color.Red;
  • Const enums don't generate runtime code.
    const enum Direction { Up, Down, Left, Right }
    const d = Direction.Up;
    // Compiles to: const d = 0; — no enum object in bundle
  • Smaller bundle size and faster performance.
    // Compiles to:
    const myColor = "red";
  • Only string values work reliably with const enums.
    // Use string values to stay safe across modules and bundlers
    const enum HttpMethod { Get = "GET", Post = "POST" }
  • Avoid const enums in published library declaration files.
    // Library consumers can't use const enums from .d.ts files
    // Use a regular enum or union type instead
    export type HttpMethod = "GET" | "POST" | "PUT";

Heterogeneous Enums

  • Mix string and numeric values in a single enum.
    enum Mixed {
      No = 0,
      Yes = "YES"
    }
  • Avoid heterogeneous enums — they are hard to reason about.
    // Confusing: mixing types makes iteration and reverse lookup unpredictable
    enum Bad { A = 0, B = "b" }
  • Use separate enums or union types instead for clarity.
    // Better: separate enums
    enum NumericStatus {
      Inactive = 0,
      Active = 1
    }
    
    enum StringStatus {
      Inactive = "inactive",
      Active = "active"
    }
  • Keep all enum members the same type to enable consistent iteration.
    // Safe: all strings — Object.values returns only the values
    enum Color { Red = "red", Green = "green", Blue = "blue" }
    const colors = Object.values(Color); // ["red", "green", "blue"]
  • TypeScript emits a warning for most heterogeneous patterns.
    // Prefer string or numeric uniformly to avoid compiler warnings
    enum Uniform { A = "a", B = "b", C = "c" } // clean

Union Types Alternative

  • Use union types instead of enums for simpler definitions.
    type Status = "active" | "inactive" | "pending";
    
    const status: Status = "active";
  • Union types are simpler and don't generate runtime code.
    // No object created — pure compile-time type
    type Role = "admin" | "user" | "guest";
  • Union types are more flexible for type checking and inference.
    type Role = "admin" | "user" | "guest";
    
    function hasAccess(role: Role): boolean {
      return role === "admin" || role === "user";
    }
  • Use as const to create both a type and runtime constants.
    const STATUSES = {
      Active: "active",
      Inactive: "inactive"
    } as const;
    
    type Status = typeof STATUSES[keyof typeof STATUSES];
    // Status = "active" | "inactive"
  • Prefer union types when values are already plain strings in your codebase.
    // API returns "GET" | "POST" directly — no enum needed
    type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
    
    function request(method: HttpMethod, url: string) { }

Tip: Use union types with as const instead of enums for most cases — they're simpler and don't have the pitfalls of enum reverse mapping.

Warning: Numeric enums can cause subtle bugs due to reverse mapping — prefer string enums or union types to avoid confusion.

TypeScript DecoratorsTypeScript Enums