TypeScript Enums: Complete Guide with Numeric and String Examples
TypeScriptEnumsBest Practices

TypeScript Enums: Complete Guide with Numeric and String Examples

January 19, 2025
10 min read
Salman Izhar

TypeScript Enums: The Complete Guide

Enums allow you to define a set of named constants. They make code more readable and maintainable.

Numeric Enums

Numeric enums are the default. They auto-increment from 0.

enum Status {
  Pending,    // 0
  Approved,   // 1
  Rejected    // 2
}

function updateStatus(status: Status) {
  if (status === Status.Pending) {
    console.log("Waiting for approval");
  }
}

updateStatus(Status.Pending);  // ✅ Type-safe

Custom Numeric Values

enum HttpStatus {
  OK = 200,
  Created = 201,
  BadRequest = 400,
  Unauthorized = 401,
  NotFound = 404,
  InternalServerError = 500
}

function handleResponse(status: HttpStatus) {
  switch (status) {
    case HttpStatus.OK:
      return "Success!";
    case HttpStatus.NotFound:
      return "Resource not found";
    default:
      return "Unknown status";
  }
}

console.log(handleResponse(HttpStatus.OK)); // "Success!"

Partial Initialization

enum Priority {
  Low = 1,      // 1
  Medium,       // 2 (auto-increments)
  High,         // 3
  Critical = 10, // 10
  Urgent        // 11 (auto-increments from 10)
}

String Enums

String enums require every member to be initialized with a string literal.

enum LogLevel {
  Error = "ERROR",
  Warning = "WARNING",
  Info = "INFO",
  Debug = "DEBUG"
}

function log(level: LogLevel, message: string) {
  console.log(`[${level}] ${message}`);
}

log(LogLevel.Error, "Something went wrong");
// Output: [ERROR] Something went wrong

Real-World Example: API Endpoints

enum ApiEndpoint {
  Users = "/api/users",
  Posts = "/api/posts",
  Comments = "/api/comments",
  Auth = "/api/auth"
}

async function fetchData(endpoint: ApiEndpoint) {
  const response = await fetch(endpoint);
  return response.json();
}

// Usage
const users = await fetchData(ApiEndpoint.Users);
const posts = await fetchData(ApiEndpoint.Posts);

Real-World Example: User Roles

enum UserRole {
  Admin = "ADMIN",
  Editor = "EDITOR",
  Viewer = "VIEWER",
  Guest = "GUEST"
}

interface User {
  id: string;
  name: string;
  role: UserRole;
}

function hasPermission(user: User, requiredRole: UserRole): boolean {
  const roleHierarchy = {
    [UserRole.Admin]: 4,
    [UserRole.Editor]: 3,
    [UserRole.Viewer]: 2,
    [UserRole.Guest]: 1
  };
  
  return roleHierarchy[user.role] >= roleHierarchy[requiredRole];
}

const user: User = {
  id: "1",
  name: "Alice",
  role: UserRole.Editor
};

console.log(hasPermission(user, UserRole.Viewer)); // true
console.log(hasPermission(user, UserRole.Admin));  // false

Const Enums

Const enums are completely removed during compilation for better performance.

const enum Direction {
  Up,
  Down,
  Left,
  Right
}

const move = Direction.Up;
// Compiles to: const move = 0;
// No enum object is generated

When to Use Const Enums

Use for: Performance-critical code ❌ Don't use if: You need reverse mapping

Reverse Mapping

Numeric enums have reverse mapping - get the enum name from its value.

enum Status {
  Pending,
  Approved,
  Rejected
}

console.log(Status[0]); // "Pending"
console.log(Status["Pending"]); // 0

// String enums don't have reverse mapping
enum Color {
  Red = "RED",
  Green = "GREEN"
}

console.log(Color["Red"]); // "RED"
console.log(Color["RED"]); // undefined (no reverse)

Enums vs Union Types

// Enum
enum Direction {
  Up = "UP",
  Down = "DOWN"
}

// Union Type (often preferred)
type Direction = "UP" | "DOWN";

// Enums: Better for related constants with behavior
// Unions: Better for simple string literals

Best Practices

1. Use string enums for better debugging 2. Use const enums for performance when you don't need reverse mapping 3. Prefer union types for simple cases 4. Document enum values for clarity

Example: Complete Enum Usage

enum OrderStatus {
  Pending = "PENDING",
  Processing = "PROCESSING",
  Shipped = "SHIPPED",
  Delivered = "DELIVERED",
  Cancelled = "CANCELLED"
}

interface Order {
  id: string;
  status: OrderStatus;
  total: number;
}

function canCancelOrder(order: Order): boolean {
  return [
    OrderStatus.Pending,
    OrderStatus.Processing
  ].includes(order.status);
}

function getStatusMessage(status: OrderStatus): string {
  switch (status) {
    case OrderStatus.Pending:
      return "Order received";
    case OrderStatus.Processing:
      return "Preparing your order";
    case OrderStatus.Shipped:
      return "On the way";
    case OrderStatus.Delivered:
      return "Delivered successfully";
    case OrderStatus.Cancelled:
      return "Order cancelled";
    default:
      const _exhaustive: never = status;
      throw new Error("Unhandled status");
  }
}

Conclusion

Enums are powerful for:

  • Defining related constants
  • Type-safe value sets
  • Better code documentation
  • Exhaustive switch statements

Choose wisely between numeric, string, and const enums based on your needs!

Get More Like This

Want articles like this in your inbox?

Join developers and founders who get practical insights on frontend, SaaS, and building better products.

S

Written by Salman Izhar

Frontend Developer specializing in React, Next.js, and building high-converting web applications.

Learn More