Logo

Home

About

Blog

Contact

Guestbook

Portfolio

Privacy

TOS

Click to navigate

  1. Home
  2. Joshua R. Lehman's Blog
  3. Enums: When and How to Use Them

Table of contents

  • Share on X
  • Discuss on X

Related Articles

Basic Types in TypeScript
TypeScript
9m
Nov 23, 2025

Basic Types in TypeScript

TypeScript's type system starts with fundamental building blocks: primitive types like string, number, and boolean, along with arrays and type annotations. Understanding these basic types is essential for writing type-safe code. This comprehensive guide walks you through each primitive type, shows you how to use type annotations effectively, and teaches you best practices for working with arrays and basic data structures in TypeScript.

#TypeScript#Types+5
Working with Arrays and Tuples
TypeScript
7m
Nov 28, 2025

Working with Arrays and Tuples

Arrays and tuples are essential for working with collections in TypeScript. While arrays hold multiple values of the same type, tuples allow you to work with fixed-length arrays where each position has a specific type. This guide covers array types, readonly patterns, tuple best practices, and practical examples that you'll use in real applications. Learn how to leverage TypeScript's type system for safer, more maintainable collection handling.

#TypeScript#Arrays+5
Understanding the TypeScript Compiler
TypeScript
6m
Nov 18, 2025

Understanding the TypeScript Compiler

The TypeScript compiler (tsc) is the engine that transforms your TypeScript code into JavaScript. Understanding how it works, what happens during compilation, and how to leverage features like watch mode will make you a more effective TypeScript developer. This guide demystifies the compilation process and shows you how to optimize your workflow with the TypeScript compiler.

#TypeScript#Compiler+5

© Joshua R. Lehman

Full Stack Developer

Crafted with passion • Built with modern web technologies

2025 • All rights reserved

Contents

  • What Are Enums?
  • Numeric Enums
    • Auto-Incrementing Values
    • Custom Numeric Values
    • Reverse Mapping
  • String Enums
    • Why Use String Enums
    • String Enum Patterns
  • Const Enums
    • How Const Enums Work
    • When to Use Const Enums
  • Heterogeneous Enums
  • Real-World Use Cases
    • HTTP Status Codes
    • User Roles and Permissions
    • Application States
    • API Response Types
  • Enum Alternatives
    • Union Types
    • Object with as const
    • When to Use Each
  • Common Enum Patterns
  • Best Practices
  • Common Pitfalls
  • Next Steps
TypeScript

Enums: When and How to Use Them

December 3, 2025•11 min read
Joshua R. Lehman
Joshua R. Lehman
Author
TypeScript enums visualization
Enums: When and How to Use Them

Enums: When and How to Use Them#

Enums (enumerations) give you a way to define a set of named constants. They make code more readable by replacing magic numbers and strings with descriptive names. When used appropriately, enums improve code maintainability and reduce errors, but TypeScript also offers compelling alternatives that are sometimes better choices.

Quick Definition: An enum is a special type that defines a collection of related constants. Think of it as a way to say "this value must be one of these specific options."

What Are Enums?

Enums allow you to define a set of named constants that represent distinct values:

enum Direction {
  North,
  South,
  East,
  West,
}
 
// Usage
let heading: Direction = Direction.North;
 
function move(direction: Direction) {
  switch (direction) {
    case Direction.North:
      console.log("Moving north");
      break;
    case Direction.South:
      console.log("Moving south");
      break;
    case Direction.East:
      console.log("Moving east");
      break;
    case Direction.West:
      console.log("Moving west");
      break;
  }
}
 
move(Direction.East); // "Moving east"

Enums exist at both compile-time (for type checking) and runtime (as actual JavaScript objects).

Numeric Enums

Numeric enums are the default in TypeScript. Each member gets assigned a numeric value.

Auto-Incrementing Values

By default, enums start at 0 and auto-increment:

enum Status {
  Pending, // 0
  InProgress, // 1
  Completed, // 2
  Failed, // 3
}
 
let currentStatus: Status = Status.Pending;
console.log(currentStatus); // 0
 
// You can use numeric values
let status: Status = 2; // Status.Completed
console.log(Status[2]); // "Completed" (reverse mapping)

Compiled JavaScript:

var Status;
(function (Status) {
  Status[(Status["Pending"] = 0)] = "Pending";
  Status[(Status["InProgress"] = 1)] = "InProgress";
  Status[(Status["Completed"] = 2)] = "Completed";
  Status[(Status["Failed"] = 3)] = "Failed";
})(Status || (Status = {}));

Custom Numeric Values

You can set specific numeric values:

enum HttpStatus {
  OK = 200,
  Created = 201,
  BadRequest = 400,
  Unauthorized = 401,
  NotFound = 404,
  ServerError = 500,
}
 
function handleResponse(status: HttpStatus) {
  if (status === HttpStatus.OK) {
    console.log("Success!");
  } else if (status >= 400) {
    console.log("Error occurred");
  }
}
 
handleResponse(HttpStatus.NotFound);

Partial initialization:

enum Priority {
  Low, // 0
  Medium = 5,
  High, // 6 (continues from previous)
  Critical = 10,
}
 
console.log(Priority.Low); // 0
console.log(Priority.Medium); // 5
console.log(Priority.High); // 6
console.log(Priority.Critical); // 10

Reverse Mapping

Numeric enums have reverse mappings—you can get the enum name from its value:

enum Color {
  Red = 1,
  Green = 2,
  Blue = 3,
}
 
// Forward mapping
let colorValue: number = Color.Red; // 1
 
// Reverse mapping
let colorName: string = Color[1]; // "Red"
 
console.log(Color[2]); // "Green"
console.log(Color[3]); // "Blue"
 
// Useful for debugging
function getColorName(value: number): string {
  return Color[value] || "Unknown";
}

Note: String enums do NOT have reverse mapping—only numeric enums do.

String Enums

String enums assign string values to each member:

enum Direction {
  North = "NORTH",
  South = "SOUTH",
  East = "EAST",
  West = "WEST",
}
 
let heading: Direction = Direction.North;
console.log(heading); // "NORTH"
 
// Must use exact enum member, not raw string
heading = "NORTH"; // ✗ Error: Type '"NORTH"' is not assignable to type 'Direction'
heading = Direction.North; // ✓ Correct

Why Use String Enums

String enums are often better than numeric enums:

1. Self-documenting in logs and debugging:

enum LogLevel {
  Debug = "DEBUG",
  Info = "INFO",
  Warning = "WARNING",
  Error = "ERROR",
}
 
function log(level: LogLevel, message: string) {
  console.log(`[${level}] ${message}`);
}
 
log(LogLevel.Error, "Something went wrong");
// Output: [ERROR] Something went wrong
// Much clearer than [3] Something went wrong

2. Serialization-friendly:

enum UserRole {
  Admin = "admin",
  Editor = "editor",
  Viewer = "viewer",
}
 
interface User {
  name: string;
  role: UserRole;
}
 
let user: User = {
  name: "Alice",
  role: UserRole.Admin,
};
 
// Serializes to readable JSON
console.log(JSON.stringify(user));
// {"name":"Alice","role":"admin"}

3. Refactoring safety:

// If you change the internal value, the enum name stays the same
enum Environment {
  Development = "dev", // Was "development", now "dev"
  Production = "prod", // Was "production", now "prod"
}
 
// All code using Environment.Development still works

String Enum Patterns

API endpoints:

enum ApiEndpoint {
  Users = "/api/users",
  Posts = "/api/posts",
  Comments = "/api/comments",
}
 
async function fetchData(endpoint: ApiEndpoint) {
  let response = await fetch(endpoint);
  return response.json();
}
 
fetchData(ApiEndpoint.Users);

Event names:

enum EventType {
  Click = "click",
  MouseMove = "mousemove",
  KeyDown = "keydown",
  Scroll = "scroll",
}
 
element.addEventListener(EventType.Click, handler);

CSS class names:

enum ButtonVariant {
  Primary = "btn-primary",
  Secondary = "btn-secondary",
  Danger = "btn-danger",
}
 
function createButton(variant: ButtonVariant) {
  return `<button class="${variant}">Click me</button>`;
}

Const Enums

Const enums are completely removed during compilation, with their values inlined:

const enum Direction {
  North = "NORTH",
  South = "SOUTH",
  East = "EAST",
  West = "WEST",
}
 
let heading = Direction.North;

Compiled JavaScript:

// Enum is completely removed!
let heading = "NORTH"; // Value is inlined

How Const Enums Work

Regular enum:

enum Color {
  Red,
  Green,
  Blue,
}
let c = Color.Red; // Compiles to: let c = Color.Red;

Const enum:

const enum Color {
  Red,
  Green,
  Blue,
}
let c = Color.Red; // Compiles to: let c = 0;

Benefits:

  • No runtime code generated
  • Smaller bundle size
  • Better performance (no object lookup)

Limitations:

  • No reverse mapping
  • Can't iterate over values
  • Members must be constant expressions

When to Use Const Enums

Use const enums when:

  • Performance is critical
  • You want minimal bundle size
  • You don't need runtime enum object
  • Values are truly constant

Example - Status codes:

const enum HttpStatus {
  OK = 200,
  NotFound = 404,
  ServerError = 500,
}
 
function handleStatus(status: HttpStatus) {
  if (status === HttpStatus.OK) {
    // Compiles to: if (status === 200)
    console.log("Success");
  }
}

Trade-off: Const enums give better performance but less flexibility. If you need to iterate over enum values or use them dynamically, use regular enums.

Heterogeneous Enums

Enums can mix string and numeric values (though this is rarely recommended):

enum BooleanLike {
  No = 0,
  Yes = "YES",
}
 
// Works but confusing
let value: BooleanLike = BooleanLike.No; // 0
let value2: BooleanLike = BooleanLike.Yes; // "YES"

Avoid Heterogeneous Enums: Mixing strings and numbers in the same enum is confusing and error-prone. Stick to all-string or all-numeric enums.

Real-World Use Cases

HTTP Status Codes

enum HttpStatus {
  // Success
  OK = 200,
  Created = 201,
  NoContent = 204,
 
  // Client Errors
  BadRequest = 400,
  Unauthorized = 401,
  Forbidden = 403,
  NotFound = 404,
 
  // Server Errors
  InternalServerError = 500,
  ServiceUnavailable = 503,
}
 
function handleApiResponse(status: HttpStatus, data: any) {
  switch (status) {
    case HttpStatus.OK:
      return { success: true, data };
    case HttpStatus.NotFound:
      return { success: false, error: "Resource not found" };
    case HttpStatus.Unauthorized:
      return { success: false, error: "Authentication required" };
    default:
      return { success: false, error: "Unknown error" };
  }
}
 
// Usage
let response = handleApiResponse(HttpStatus.OK, { user: "Alice" });

User Roles and Permissions

enum UserRole {
  Admin = "admin",
  Moderator = "moderator",
  Editor = "editor",
  Viewer = "viewer",
}
 
enum Permission {
  Read = "read",
  Write = "write",
  Delete = "delete",
  Manage = "manage",
}
 
const rolePermissions: Record<UserRole, Permission[]> = {
  [UserRole.Admin]: [
    Permission.Read,
    Permission.Write,
    Permission.Delete,
    Permission.Manage,
  ],
  [UserRole.Moderator]: [Permission.Read, Permission.Write, Permission.Delete],
  [UserRole.Editor]: [Permission.Read, Permission.Write],
  [UserRole.Viewer]: [Permission.Read],
};
 
function hasPermission(role: UserRole, permission: Permission): boolean {
  return rolePermissions[role].includes(permission);
}
 
// Usage
let canDelete = hasPermission(UserRole.Editor, Permission.Delete); // false
let canRead = hasPermission(UserRole.Viewer, Permission.Read); // true

Application States

enum LoadingState {
  Idle = "idle",
  Loading = "loading",
  Success = "success",
  Error = "error",
}
 
interface DataState<T> {
  status: LoadingState;
  data: T | null;
  error: string | null;
}
 
function createInitialState<T>(): DataState<T> {
  return {
    status: LoadingState.Idle,
    data: null,
    error: null,
  };
}
 
async function fetchData<T>(url: string): Promise<DataState<T>> {
  let state: DataState<T> = {
    status: LoadingState.Loading,
    data: null,
    error: null,
  };
 
  try {
    let response = await fetch(url);
    let data = await response.json();
    return {
      status: LoadingState.Success,
      data,
      error: null,
    };
  } catch (error) {
    return {
      status: LoadingState.Error,
      data: null,
      error: error.message,
    };
  }
}

API Response Types

enum ResponseType {
  JSON = "json",
  XML = "xml",
  Text = "text",
  Blob = "blob",
}
 
async function apiRequest(url: string, type: ResponseType = ResponseType.JSON) {
  let response = await fetch(url);
 
  switch (type) {
    case ResponseType.JSON:
      return await response.json();
    case ResponseType.XML:
      return await response.text();
    case ResponseType.Text:
      return await response.text();
    case ResponseType.Blob:
      return await response.blob();
  }
}
 
// Usage
let data = await apiRequest("/api/users", ResponseType.JSON);
let image = await apiRequest("/images/logo.png", ResponseType.Blob);

Enum Alternatives

TypeScript offers alternatives that are sometimes better than enums.

Union Types

For simple cases, union types are often cleaner:

// Using enum
enum Size {
  Small = "small",
  Medium = "medium",
  Large = "large",
}
 
// Using union type
type Size = "small" | "medium" | "large";
 
// Usage is similar
function setSize(size: Size) {
  console.log(`Size set to ${size}`);
}
 
setSize("medium"); // ✓ Works with union type
// setSize(Size.Medium); // Required with enum

Pros of union types:

  • No runtime code
  • Can use string literals directly
  • Simpler syntax
  • Better for serialization

Cons of union types:

  • No autocomplete for all values
  • Can't iterate over values
  • No namespace grouping

Object with as const

Objects with as const provide enum-like behavior:

// Using enum
enum Direction {
  North = "NORTH",
  South = "SOUTH",
  East = "EAST",
  West = "WEST",
}
 
// Using object with as const
const Direction = {
  North: "NORTH",
  South: "SOUTH",
  East: "EAST",
  West: "WEST",
} as const;
 
type Direction = (typeof Direction)[keyof typeof Direction];
 
// Usage
let heading: Direction = Direction.North;

Advantages of objects:

  • Works like a regular JavaScript object
  • Can iterate over values
  • Better tree-shaking in some bundlers
  • More flexible (can add methods)
const Status = {
  Pending: "pending",
  Active: "active",
  Completed: "completed",
 
  // Can add helper methods
  isActive: (status: string) => status === Status.Active,
} as const;
 
type Status = (typeof Status)[keyof typeof Status];

When to Use Each

Use enums when:

  • You need numeric values with reverse mapping
  • You want strong namespace grouping
  • The values are truly a closed set of options
  • Working with external APIs that expect specific values

Use union types when:

  • Values are simple strings or numbers
  • You want minimal runtime overhead
  • Serialization is important
  • The type is consumed by external code

Use objects with as const when:

  • You need to iterate over values
  • You want to add helper methods
  • You need a plain JavaScript object
  • Working in a codebase that avoids enums

Modern Trend: Many TypeScript developers prefer union types and as const objects over enums for their simplicity and zero runtime cost.

Common Enum Patterns

1. Enum with helper functions:

enum Status {
  Draft = "draft",
  Published = "published",
  Archived = "archived",
}
 
namespace Status {
  export function isEditable(status: Status): boolean {
    return status === Status.Draft;
  }
 
  export function canView(status: Status): boolean {
    return status !== Status.Archived;
  }
}
 
// Usage
if (Status.isEditable(currentStatus)) {
  // Allow editing
}

2. Enum iteration:

enum Color {
  Red = "red",
  Green = "green",
  Blue = "blue",
}
 
// Get all enum values
let colors = Object.values(Color);
console.log(colors); // ["red", "green", "blue"]
 
// Get all enum keys
let colorNames = Object.keys(Color);
console.log(colorNames); // ["Red", "Green", "Blue"]

3. Type-safe enum lookup:

enum Status {
  Active = "active",
  Inactive = "inactive",
}
 
function parseStatus(value: string): Status | null {
  return Object.values(Status).includes(value as Status)
    ? (value as Status)
    : null;
}
 
let status = parseStatus("active"); // Status.Active
let invalid = parseStatus("unknown"); // null

Best Practices

1. Use string enums for better debugging:

// ✓ Good - readable in logs
enum LogLevel {
  Debug = "DEBUG",
  Info = "INFO",
}
 
// ✗ Avoid - confusing in logs
enum LogLevel {
  Debug, // 0
  Info, // 1
}

2. Use PascalCase for enum names, UPPER_CASE for members:

// ✓ Good
enum HttpStatus {
  OK = 200,
  NOT_FOUND = 404,
}
 
// ✗ Inconsistent
enum httpStatus {
  ok = 200,
  notFound = 404,
}

3. Keep enums focused and cohesive:

// ✓ Good - focused
enum UserRole {
  Admin = "admin",
  User = "user",
}
 
enum DocumentStatus {
  Draft = "draft",
  Published = "published",
}
 
// ✗ Bad - mixing unrelated concepts
enum AppConstants {
  AdminRole = "admin",
  MaxRetries = 3,
  TimeoutMs = 5000,
}

4. Consider alternatives before using enums:

// For simple cases, union types are cleaner
type Status = "active" | "inactive" | "pending";
 
// Instead of
enum Status {
  Active = "active",
  Inactive = "inactive",
  Pending = "pending",
}

5. Document enum purpose and values:

/**
 * Represents the current state of an order
 */
enum OrderStatus {
  /** Order has been placed but not processed */
  Pending = "pending",
 
  /** Order is being prepared */
  Processing = "processing",
 
  /** Order has been shipped */
  Shipped = "shipped",
 
  /** Order has been delivered */
  Delivered = "delivered",
}

Common Pitfalls

Pitfall 1: Assuming type safety with numeric enums

enum Status {
  Pending,
  Active,
  Completed,
}
 
let status: Status = 99; // ✓ Compiles! No error
// Numeric enums allow any number

Solution: Use string enums for stricter type safety:

enum Status {
  Pending = "pending",
  Active = "active",
  Completed = "completed",
}
 
let status: Status = "unknown"; // ✗ Error: Type '"unknown"' is not assignable

Pitfall 2: Using const enum with external modules

// library.ts
export const enum Direction {
  North,
  South,
}
 
// main.ts
import { Direction } from "./library";
let dir = Direction.North; // May not work with some bundlers

Pitfall 3: Modifying enum objects at runtime

enum Color {
  Red,
  Green,
  Blue,
}
 
// Don't do this!
Color.Red = 99; // Possible but breaks type safety

Pitfall 4: Over-using enums

// ✗ Overkill for two values
enum BinaryChoice {
  Yes = "yes",
  No = "no",
}
 
// ✓ Better
type BinaryChoice = "yes" | "no";
// or just use boolean!

Next Steps

You now understand enums and their alternatives in TypeScript! Here's what to explore next:

Practice:

  • Refactor magic strings in your code to enums
  • Try using union types instead of enums
  • Experiment with as const objects
  • Compare bundle sizes with const enums

Continue Learning:

  • Interfaces and object types
  • Type aliases and advanced types
  • Generics for reusable types
  • Utility types

Congratulations! You've mastered TypeScript enums and their alternatives. Use this knowledge to write more expressive, maintainable code. Remember: enums are a tool, not a requirement—choose the right approach for each situation.


Questions about enums? Debating between enums and union types? Share your use case in the comments and let's discuss the best approach!