TypeScript Enums or Union Types: Which One Should You Use?

Sanya Gubrani on 2024-09-14

When it comes to TypeScript, developers often find themselves debating whether to use enums or union types — both are great, but each has its time and place. Let’s break it down in simple terms and figure out when to use what.

First off, what are we talking about? Both enums and union types are ways to tell TypeScript, “Hey, this thing can only be one of these specific values.”

export enum Status {
  Successful = 'successful',
  Failed = 'failed',
  Skipped = 'skipped',
}
export type Status = 'successful' | 'failed' | 'skipped';

Both of these are saying, “The Status can only be successful, failed, or skipped.” Simple, right? But the way they work behind the scenes and their impact on your code are different. Now, why would you choose one over the other? Let’s talk about the pros and cons.

Performance and Bundle Size

Enums get compiled into JavaScript objects. This means that for every enum, there’s a bit of extra code generated. For instance, the Status enum above will generate something like this:

var Status;
(function (Status) {
    Status["Successful"] = "successful";
    Status["Failed"] = "unsuccessful";
    Status["Skipped"] = "skipped";
})(Status || (Status = {}));

This extra code can slightly increase the size of your final bundle. For small projects, this isn’t a big deal. But for larger projects, especially web apps where every byte counts, it might make you think twice.

Union types, on the other hand, are like a list of options. They’re simpler and don’t create any extra code. This means your final code will be smaller, which can be important in performance-sensitive projects.

Flexibility

One area where Union Types shine is flexibility, especially when dealing with APIs or JSON responses. Often, external APIs will send status values as simple strings, and using Union Types allows you to work with these values directly without converting them. For example:

// Receiving a status from an API
const apiResponse: Status = 'successful';

With Enums, you might have to convert these values back and forth, which can add a small amount of complexity.

Refactoring and Maintenance

One of the key things to think about when choosing between Enums and Union Types is how easy it will be to make changes in the future.

export enum Status {
  Successful = 'successful',
  Failed = 'unsuccessful', // Changed here
  Skipped = 'skipped',
}

No matter how many files use Status.Failed, they will automatically update to use 'unsuccessful'. This makes Enums really handy when your project grows and you need to maintain consistency across different parts of the codebase.

That being said, modern IDEs like VS Code can help with this. They have tools to refactor these string values automatically, so this isn’t as painful as it used to be. But still, Enums make it slightly easier if you anticipate a lot of changes.

Numbers in Enums vs Union Types

When working with numbers or integers in TypeScript, enums are generally a better choice than union types. Numeric enums provide clear labels for numbers, making code more readable.

enum StatusCode {
    OK = 200,
    BadRequest = 400,
    NotFound = 404
}

With union types, you only list numbers, which makes the code less descriptive.

type StatusCode = 200 | 400 | 404;

You can iterate over enums or map numbers to their labels, a feature not available with union types.

What About Developer Experience?

Both Enums and Union Types give you great auto-completion and type safety in TypeScript. Your editor will help you by suggesting the possible values you can use. However, some developers prefer the look and structure of Enums because they make it visually clear that a variable can only be one of the predefined options. Union Types, on the other hand, can sometimes look like hardcoded strings, which might make the code feel less structured to some people.

Choosing Between Enums and Union Types

At the end of the day, the choice between Enums and Union Types depends on your specific needs. So, when should you use each?

Use enums when:

Use union types when:

For Example: Let’s say you’re building a weather app. For the days of the week, an enum might be perfect:

enum DayOfWeek {
 Monday = 'Monday',
 Tuesday = 'Tuesday',
 // … and so on
}

But for the weather conditions, a union type might be better:

type WeatherCondition = 'sunny' | 'rainy' | 'cloudy' | 'snowy';

Why? Because you might get these weather conditions as strings from a weather API, and using a union type makes it super easy to work with that data.

Consistency Matters

A common piece of advice from experienced developers is to pick one and stay consistent across your project. This makes the code easier to maintain and understand for everyone working on it. However, there are valid cases where you might switch between Enums and Union Types depending on the needs of your project.

For example, if you have a set of values that are unlikely to change, and you need a lightweight solution, Union Types might be perfect. On the other hand, if you expect to frequently refactor, Enums could save you time and reduce errors.

At the end of the day, both enums and union types are useful tools in TypeScript. The “best” choice depends on your specific needs. And here’s a little secret: many developers mix and match, using enums in some parts of their code and union types in others. That’s totally okay!