Technology · TypeScript
TypeScript Enums Best Practices
Use enums effectively for type-safe constants and avoid common pitfalls.
TL;DR
- 01Use string enums for better debugging and serialization.
- 02Use const enums to avoid runtime overhead.
- 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 constinstead 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.