Union Types vs Enums in TypeScript: Choosing the Right Approach for Your Codebase

Soroush on 2025-02-13

Introduction

When working with TypeScript, developers often face the decision of whether to use union types or enums to represent a fixed set of values. While enums have been a long-standing feature in TypeScript, union types offer a more modern and lightweight alternative.

In this article, we will compare union types and enums, explore their pros and cons, and determine when one is preferable over the other.

Using Union Types

Union types allow you to define a variable that can have one of a specific set of values, typically represented as a string or number.

Example: Defining Deposit States Using Union Types

type DepositState = 'DRAFT' | 'ACCEPTED' | 'REJECTED';

function processDeposit(state: DepositState) {
  if (state === 'DRAFT') {
    console.log("Deposit is in draft state");
  } else if (state === 'ACCEPTED') {
    console.log("Deposit has been accepted");
  } else {
    console.log("Deposit was rejected");
  }
}

Advantages of Union Types:

Compile-time Type Safety: If a value is used outside of the defined union, TypeScript will throw an error.

Autocomplete Support: When typing a value, TypeScript will suggest all possible valid options.

Lightweight Compilation: Union types do not generate any extra JavaScript code.

No Additional Maintenance: There’s no need to define additional const or enum values, reducing code overhead.

Disadvantages of Union Types:

Scalability Issues: If the number of values grows significantly, managing and updating union types can become cumbersome.

Lack of Centralization: Union types are spread across the codebase rather than being defined in one centralized place like an enum.

Refactoring Complexity: When renaming or restructuring values, it can be harder to track down every usage compared to modifying a single enum object.

Example of Type Checking in Action:

If you accidentally pass an invalid state:

processDeposit('PENDING'); // ❌ TypeScript Error: Argument of type 'PENDING' is not assignable to parameter of type 'DepositState'.

Using Enums

Enums provide a way to define a collection of named constants. These are compiled into JavaScript objects.

Example: Defining Deposit States Using Enums

enum DepositState {
  DRAFT = 'DRAFT',
  ACCEPTED = 'ACCEPTED',
  REJECTED = 'REJECTED'
}

function processDeposit(state: DepositState) {
  if (state === DepositState.DRAFT) {
    console.log("Deposit is in draft state");
  } else if (state === DepositState.ACCEPTED) {
    console.log("Deposit has been accepted");
  } else {
    console.log("Deposit was rejected");
  }
}

Advantages of Enums:

Readability & Maintainability: Enums make it clear where values are defined and provide a structured way to group related constants.

Centralized Definition: Instead of scattered string literals, all possible values exist in one place.

Easier Refactoring: Renaming or updating values is more manageable.

However, enums also introduce some drawbacks:

Performance Overhead: Enums are compiled into JavaScript objects, which means they introduce additional runtime overhead.

Extra Code Generation: Unlike union types, enums generate JavaScript code, which increases bundle size.

Not Always Necessary: For simple string values, using union types often achieves the same goal without the added complexity.

Comparing Performance: Union Types vs Enums

One important factor to consider is performance. Let’s look at how TypeScript compiles each approach.

Compiled JavaScript Code for Union Types:

function processDeposit(state) {
  if (state === "DRAFT") {
    console.log("Deposit is in draft state");
  } else if (state === "ACCEPTED") {
    console.log("Deposit has been accepted");
  } else {
    console.log("Deposit was rejected");
  }
}

Union types do not generate extra JavaScript code, keeping the output lightweight.

Compiled JavaScript Code for Enums:

var DepositState;
(function (DepositState) {
  DepositState["DRAFT"] = "DRAFT";
  DepositState["ACCEPTED"] = "ACCEPTED";
  DepositState["REJECTED"] = "REJECTED";
})(DepositState || (DepositState = {}));

Enums generate additional JavaScript code, which may impact performance in large-scale applications.

When Should You Use Union Types vs Enums?

Use Union Types when:

Use Enums when:

General Rule of Thumb:

Conclusion

While both union types and enums serve similar purposes in TypeScript, they offer different advantages. Union types provide a lightweight, type-safe, and performance-friendly approach, while enums offer better maintainability and readability when dealing with structured constants.

In most modern TypeScript projects, union types are preferred due to their simplicity, better type inference, and reduced JavaScript output. However, if your project requires a well-structured constant definition that is easy to manage and refactor, enums may be the better choice.

Ultimately, the choice depends on your use case, project complexity, and performance considerations.

What’s your preference? Do you use union types or enums in your projects? Let us know in the comments!