avatarDr. Derek Austin 🥳

Summary

TypeScript Enums are critiqued for their runtime and compilation overhead, rigidity, and refactoring challenges, whereas Union Types are praised for their simplicity, flexibility, and improved performance and maintainability, with better IntelliSense support in Visual Studio Code.

Abstract

The article presents a compelling argument against the use of TypeScript Enums, pointing out their drawbacks such as increased runtime and compilation overhead, limited flexibility, and difficulties in refactoring. It advocates for the adoption of Union Types as a superior alternative, highlighting their lightweight nature, ease of modification and extension, and their positive impact on code performance and maintainability. The author also emphasizes the superior IntelliSense support for Union Types in Visual Studio Code, which enhances the development experience. A step-by-step guide is provided for developers looking to migrate from Enums to Union Types in their TypeScript codebases.

Opinions

  • Enums are seen as suboptimal due to their associated runtime and compilation overhead, which can be detrimental in performance-critical applications.
  • The author expresses that Enums are too rigid, making it difficult to modify or extend their values without affecting the entire codebase.
  • Refactoring Enums is considered problematic, with a high risk of introducing bugs due to the need to update every instance where an Enum is used.
  • Union Types are favored for their simplicity, efficiency, and the fact that they do not introduce additional runtime objects, thus reducing memory usage and file size.
  • The flexibility of Union Types is highlighted, as they can be easily combined and extended using type operations, enhancing code composability and reusability.
  • The author's preference for Union Types is clear, based on their own experience with IntelliSense in Visual Studio Code, which provides better insights and autocompletion for Union Types compared to Enums.
  • The conclusion strongly suggests that the author finds Enums reminiscent of Java's paradigms, which they consider less suitable for TypeScript development, especially when working with string literals and in scenarios like passing Tailwind CSS classes as props in React.

Why TypeScript Enums Are Terrible But Union Types Are Great

A deep dive into the pitfalls of TypeScript Enums and the power of Union Types, or why I refactor all my Enums to Union Types.

Photo by Tatiana Rodriguez on Unsplash

Introduction: TypeScript Enums vs. Union Types

As developers, we’re constantly looking for ways to improve our codebases and create more maintainable, flexible, and robust software. One of the best ways to achieve this is by leveraging the strengths of different programming paradigms and language features. In this article, we’ll explore why TypeScript Enums are a suboptimal choice for many use cases and how Union Types can offer a superior alternative.

The Multiple Problems with TypeScript Enums

Enums, short for enumerations, are a concept borrowed from languages like C# and Java. They allow developers to define a set of named constants that represent a specific type. At first glance, Enums in TypeScript seem like a great way to improve type safety and code readability. However, they come with a set of drawbacks that may outweigh their benefits in certain scenarios.

Runtime and Compilation Overhead

One of the primary issues with Enums is that they add both runtime and compilation overhead to your codebase. TypeScript Enums are compiled into JavaScript objects with keys for both the names and values of the Enum members. This results in larger output files and additional memory consumption, which can be particularly problematic in performance-critical applications.

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

// Compiled JavaScript
"use strict";
var Direction;
(function (Direction) {
  Direction[(Direction["Up"] = 0)] = "Up";
  Direction[(Direction["Down"] = 1)] = "Down";
  Direction[(Direction["Left"] = 2)] = "Left";
  Direction[(Direction["Right"] = 3)] = "Right";
})(Direction || (Direction = {}));

Limited Flexibility

Enums are also quite rigid, providing limited flexibility when it comes to modifying or extending their values. For example, if you wanted to add a new member to an Enum, you’d need to update every place in your codebase that uses that Enum, which can be time-consuming and error-prone.

Refactoring Challenges

Refactoring Enums can be challenging, especially when it comes to renaming members. Since Enums are compiled into JavaScript objects with keys for both the names and values of the Enum members, renaming an Enum member might break your code in unexpected ways, as it also changes the corresponding value. This can lead to subtle bugs that are difficult to track down.

Embracing Union Types Instead of TypeScript Enums

In contrast to Enums, Union Types are a more flexible, lightweight, and maintainable alternative for representing a fixed set of values. They offer several advantages over Enums, which we’ll discuss below.

Simplicity and Performance

Union Types are simpler than Enums, as they don’t require any additional runtime or compilation overhead. Instead of creating a separate object, Union Types define a new type that is the union of a set of literal types. This makes them more efficient in terms of both memory usage and file size.

type Direction = "Up" | "Down" | "Left" | "Right";

Improved Flexibility and Extensibility

Union Types are also more flexible and extensible than Enums. Adding or removing members from a Union Type is as simple as updating the type definition, without the need to modify other parts of your codebase. Additionally, Union Types can be combined using intersections and other type operations, allowing for greater composability and reusability.

type DiagonalDirection = "UpLeft" | "UpRight" | "DownLeft" | "DownRight";
type AllDirections = Direction | DiagonalDirection;

Easier Refactoring

Refactoring Union Types is more straightforward than refactoring Enums. Since Union Types don’t generate separate JavaScript objects with keys for both the names and values, renaming a member of a Union Type doesn’t affect the runtime behavior of your code. This makes it easier to refactor your code without introducing subtle bugs.

Comparing IntelliSense for Enums vs. Union Types

Visual Studio Code (VS Code) is a popular code editor that offers powerful IntelliSense features, which can enhance the development experience when working with TypeScript.

Both Enums and Union Types benefit from excellent IntelliSense support in VS Code, with some differences in how the information is presented.

IntelliSense for Enums

First, let’s explore the IntelliSense support for Enums in VS Code, which is good but not great.

When using Enums, VS Code’s IntelliSense provides autocompletion for both the Enum type and its members. This can help speed up the development process by reducing the likelihood of typos and other errors.

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

function move(direction: Direction) {
  // ...
}

// Autocomplete suggests Enum members when calling the move function
move(Direction.Up);

Additionally, hovering over an Enum member will display the value associated with that member, offering further insights into your code.

IntelliSense for Union Types

VS Code’s IntelliSense support for Union Types is even better than it is for Enums, at least if you prefer working with strings like "Up".

When using Union Types, VS Code provides autocompletion for the type members, making it easy to write code that uses the defined set of values.

type Direction = "Up" | "Down" | "Left" | "Right";

function move(direction: Direction) {
  // ...
}

// Autocomplete suggests Union Type members when calling the move function
move("Up");

Furthermore, hovering over a Union Type member will display the type information, which can help you understand the structure of your code and the valid values for a given type.

Comparing IntelliSense for Enums vs. Union Types

Both Enums and Union Types have excellent support in VS Code’s IntelliSense, providing autocompletion and type information when needed. However, there are some subtle differences in the way this information is presented:

  1. Member Values: When hovering over an Enum member, VS Code displays the value associated with the member (e.g., Direction.Up = 0). In contrast, hovering over a Union Type member shows the type information (e.g., Direction = "Up" | "Down" | "Left" | "Right").
  2. Type Composition: Union Types offer greater flexibility in terms of type composition and can be easily combined using intersections and other type operations. IntelliSense support for complex Union Types can provide valuable insights into the structure and valid values of these types, making it easier to work with them.

Personally, I find it much easier to work with Union Types than Enums when it comes to Intellisense. Usually I’m concerned with questions like:

  1. “What value is being used here?”
  2. “What values are even valid here?”
  3. “Why can’t I just pass in a string to this enum?”

When choosing between Enums and Union Types, consider the trade-offs discussed earlier in this article, such as maintainability, flexibility, and performance, but also consider which IntelliSense experience you prefer. For me, this is the deciding factor in favor of Union Types.

Migrating from Enums to Union Types

If you’re convinced that Union Types are a better fit for your use case, you might be wondering how to migrate your existing codebase from Enums to Union Types. Here’s a step-by-step guide to help you through the process:

1 — Identify the Enums to be replaced

Before making any changes, identify the Enums in your codebase that you’d like to replace with Union Types. Focus on Enums that are causing the most pain in terms of maintainability, flexibility, or performance.

2 — Create the Union Type

Define a new Union Type that represents the same set of values as the Enum. Use string literals to define the members of the Union Type.

// Replace this Enum:
enum Direction {
  Up,
  Down,
  Left,
  Right,
}

// With this Union Type:
type Direction = "Up" | "Down" | "Left" | "Right";

3 — Update type annotations

Replace all type annotations in your code that reference the Enum with the new Union Type.

// Before
function move(direction: Direction) {
  // ...
}

// After
function move(direction: Direction) {
  // ...
}

4 — Replace Enum member references

Update all references to Enum members with their corresponding string literals.

// Before
move(Direction.Up);

// After
move("Up");

5 — Remove the Enum

Once all references to the Enum have been updated, you can safely remove it from your codebase and start enjoying your new Union Types.

By following these steps, you can gradually migrate your codebase from Enums to Union Types, reaping the benefits of improved maintainability, flexibility, and performance along with better IntelliSense hints.

Conclusion: Enums Are Just Too Java For My Taste

Enums come with some drawbacks that may hinder the development and maintenance of your TypeScript codebase, especially if you care about seeing the values you’re actually using (strings or otherwise) at a glance.

Personally I pass around a lot of Tailwind CSS classes as props in my React TSX, and being able to see at a glance what these are is incredibly useful:

import classNames from "classnames"

function BarbieHouse({
  bgColor,
}: {
  bgColor: "bg-pink-400" | "bg-purple-400"
}) {
  return <div className={classNames("h-24 w-24", bgColor)}>🏠</div>
}

You can try this particular example in Code Sandbox if you’d like to see my favorite use case for string Union Types in React TypeScript.

Union Types offer a more flexible, lightweight, and maintainable alternative for representing fixed sets of values, without the extra mental load and lookup time associated with TypeScript Enums.

By understanding the trade-offs and leveraging the strengths of Union Types, you can create more robust and efficient TypeScript applications.

Happy coding! ☕

Photo by Aarón Blanco Tejedor on Unsplash
Typescript
JavaScript
Programming
Software Development
Software Engineering
Recommended from ReadMedium