TypeScript enum versus const enum

Date: September 25, 2020 Last modified: September 25, 2020

Today I found that there is a difference between enum and const enum in TypeScript in the generated JavaScript. Namely:

  • with a non-const enum the compiler generates an object with key-value pairs.

  • with a const enum the definition alone doesn't generate any code, only usage of the enum does.

Note: all generated JavaScript code is generated from the TypeScript v4.1 compiler.

non-const enum definition

Let's say we wanted to rate desserts, we might have a rating scale defined as an enumeration. Using the enum construct in TypeScript we might define something like:

enum DessertRating {
  Delicious,
  ToDieFor,
  Scrumptious,
}

TypeScript generates the following JavaScript:

var DessertRating;
(function (DessertRating) {
    DessertRating[DessertRating["Delicious"] = 0] = "Delicious";
    DessertRating[DessertRating["ToDieFor"] = 1] = "ToDieFor";
    DessertRating[DessertRating["Scrumptious"] = 2] = "Scrumptious";
})(DessertRating || (DessertRating = {}));

const enum definition

Now we might want to rate our day in our app so we might have a scale defined as a const enum for DayRating like so:

const enum DayRating {
  Amazing,
  Wonderful,
  Engaging,
}

For the const enum definition case this would yield no JavaScript output.

enum Usage

Now you might be wondering what do usages of the enum values look like in each case.

const dessertRating = DessertRating.Scrumptious;

This generates the following JavaScript as output:

const dessertRating = DessertRating.Scrumptious;

For the case of const enum we might write JavaScript like so:

const dayRating = DayRating.Amazing;

Which would generate the following JavaScript:

const dayRating = 0 /* Amazing */;

This is known as inlining.

When can the TypeScript typechecker not inline?

If you need to compute any enum value then you may not be able to take advantage of the lower bloat benefits of using const enum in more places.

For example, suppose we have a configuration file generated by our deploy process that is imported and a value in there is queried to generate a derived value for the value of any enum value, then we would not be able to define it as a const enum.

Let's say this is the generated config:

{
  "currentInstanceSize": "t3.medium"
}

And we try to use the config.currentInstanceSize as the string value for one of our enum values like so:

import config from './config';

const enum InstanceSize {
  t3_small  = 't3.small',
  t3_medium = 't3.medium',
  t3_large  = 't3.large',
  current = config.currentInstanceSize,
}

Then the TypeScript typechecker would say something like the following in our error output:

Computed values are not permitted in an enum with string valued members.

Another limitation on const enum definition are that you cannot do a dynamic lookup of the value from a string, such as:

const lookup = 'm3_small';
const instanceSize = InstanceSize[lookup];

This yields the typechecker error of:

A const enum member can only be accessed using a string literal.

Yay for type errors letting us know when we can't do something instead of waiting until runtime for something to blow up in unpredicatable ways.

What is an ambient enum?

Ambient enums are those that have the declare keyword prefixed to the definition. These are used to define the shape of an existing enum type.

This can look like the following:

declare enum DOW {
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday,
  Sunday,
}

There are other differences and limitations that are enumerated (I couldn't help myself) here: TypeScript Handbook: Enums