TypeScript | Union Types vs Enums

When I started my programming journey, I used C#, which is common to implement Enums. Therefore, when I started using TypeScript it was easy to implement Enums. While working with a new team and a new TypeScript project, I noticed some code implementing something along the following lines:

type VehicleType = 'SUV' | 'SEDAN' | 'TRUCK';

I did research about this as I have never seen this annotation in the past. I quickly came across the terminology for this practice in TypeScript: String Literals Unions.

I was curious to know why there were no Enums in the project. Therefore, I decided to run a series of experiments to determine the differences between using string literal unions and enums,

Code Size Difference

You will see a difference in the code size once TypeScript code is compiled into JavaScript code. Using string literal unions will “reduce” or not change the size of your code. On the other hand, using enums will increase the code size. However, if you want to see this in action, we can run a quick test.

Testing Code Size after Compiling to JavaScript

In a new folder, create a new file call index.ts. Also, generate a package.json file using npm init and install typescript using npm i -D typescript.

Then, populate the index.ts file using the previous TypeScript example

type VehicleType = 'SUV' | 'SEDAN' | 'TRUCK';

Finally, compile it into JavaScript using typescript:

npx tsc index.ts

You will notice there is generated an index.js file. If you open it, you will notice it is empty. Hence, the size of the index.js file is 0KB.

If we update the logic in our index.ts file to generate an Enum, it will look like the following:

enum VehicleType {
  SUV,
  SEDAN,
  TRUCK,
  BUS,
  MOTORCYCLE
}

Once updated the index.ts file, compile it into JavaScript again. The results are the following:

var VehicleType;
(function (VehicleType) {
    VehicleType[VehicleType["SUV"] = 0] = "SUV";
    VehicleType[VehicleType["SEDAN"] = 1] = "SEDAN";
    VehicleType[VehicleType["TRUCK"] = 2] = "TRUCK";
    VehicleType[VehicleType["BUS"] = 3] = "BUS";
    VehicleType[VehicleType["MOTORCYCLE"] = 4] = "MOTORCYCLE";
})(VehicleType || (VehicleType = {}));

And the size of the index.js file will be 1KB.

Comparing Values when Running Conditional Checks

It was common to use enums to run conditional checks using the if or switch statements.

In this way, you could quickly pick and choose from a set of options to verify variables or property values matched any of the enum values. Luckily, this is not needed anymore as IDEs are powerful enough to suggest values when using string literal unions.

Enum Values Based on Order

It is worth mentioning that enums values will be numeric based on the order provided. For example, the values of the following enum

enum VehicleType {
  SUV,
  SEDAN,
  TRUCK,
  BUS,
  MOTORCYCLE
}

The values for SUV, SEDAN, TRUCK, BUS, MOTORCYCLE will be 1, 2 ,3, 4, 5 respectively.

The problem with this is once you move SUV to the bottom, its value will no longer be 1. This could generate unexpected logical errors when running conditional checks against numbers.

Using Enum Strings

A good alternative is to use enum strings. This will prevent automatically assigning a numerical value for each enum.

enum VehicleType {
  SUV = 'SUV',
  SEDAN = 'SEDAN',
  TRUCK = 'TRUCK',
  BUS = 'BUS',
  MOTORCYCLE = 'MOTORCYCLE'
}

It is important to mention that once the enum turns to an enum string, you cannot use them combined. For example, attempting to use regular enums and enum strings like this example will not work:

enum VehicleType {
  SUV = 'SUV',
  SEDAN = 'SEDAN',
  TRUCK = 'TRUCK',
  BUS = 'BUS',
  MOTORCYCLE
}

If attempting to compile this into JavaScript, you will see the following error:

error TS1061: Enum member must have initializer.

Unions Types, Not Union Strings

We refer often to string literal unions to define a type that could have multiple strings. However, you can make unions with other kinds of values different from strings:

type COLORS = 'RED' | 'BLUE' | 0 | [];

This gives you extra flexibility in the type definition. However, that extra flexibility could lead to unexpected behavior in the code when not having a clear understanding of the type of a variable, which could be a string, or a number, or an array of objects, etc.

Cannot Iterate Over Unions Types

Enums are objects where each property key could have a value automatically assigned or a string in case we manually define the value. Therefore, you could iterate the property values just like any other object:

export enum VehicleType {
  SUV = 'SUV',
  SEDAN = 'SEDAN',
  TRUCK = 'TRUCK',
  BUS = 'BUS',
  MOTORCYCLE = 'MOTORCYCLE'
}

for (const type in VehicleType) {
  console.log(type);
}

Object.keys(VehicleType).forEach((key) => {
  console.log('key ', key);
})

Attempting to do something similar with union types will result in a bug when trying to execute the code

type VehicleType = 'SUV' | 'SEDAN' | 'TRUCK' | 'BUS' | 'MOTORCYCLE';

for (const type in VehicleType) {
  // my logic in here
  console.log(type);
}

Object.keys(VehicleType).forEach((key) => {
  console.log('key ', key);
})

Running the previous example will throw the following error:

error TS2693: 'VehicleType' only refers to a type, but is being used as a value here.

When to Use Union Types and Enums?

If you are looking to optimize your code and reduce the bundle size at the moment of compiling into JavaScript, it is recommended to use union types as it won’t generate extra code or JavaScript objects.

If you will be needed to run iterations over a set of values, enums are the way to go even if that means sacrificing in optimizing the code size.

If you know of other cases where is best to use union types or enums, share them with me in the comments or in any of my social media channels.

More TypeScript Tips!

There is a list of TypeScript tips you might be interested in checking out

Did you like this TypeScript tip?

Share your thoughts by replying on Twitter of Become A Better Programmer or to personal my Twitter account.