TypeScript enums explained

This article explains the difference between Typescript’s enum, const enum, declare enum, and declare const enum identifiers. Caveat: I don’t recommend you use any of these enums most of the time. See the recommendation section at the end for details.

You can follow along with these explanations in this TypeScript playground.

enum

enum Cheese { Brie, Cheddar }

First, a plain old enum. The JavaScript transpiler will emit a lookup table. The lookup table looks like this:

var Cheese;
(function (Cheese) {
Cheese[Cheese["Brie"] = 0] = "Brie";
Cheese[Cheese["Cheddar"] = 1] = "Cheddar";
})(Cheese || (Cheese = {}));

Then when you have Cheese.Brie in TypeScript, it emits Cheese.Brie in JavaScript which evaluates to 0. Cheese[0] emits Cheese[0] and actually evaluates to “Brie”. The reverse lookup options is unique to enums and can be pretty convenient.

const enum

const enum Bread { Rye, Wheat }

No code is actually emitted for this! Its values are inlined. The following variations emit the value 0 itself in JavaScript:

Bread.Rye
Bread['Rye']

Inlining might be useful for performance reasons, although as with all performance optimizations be sure to take note of the trade-off of readability that you’re signing up for.

But what about Bread[0]? This will error out at runtime and your compiler should catch it. There’s no lookup table and the compiler doesn’t inline here.

Note that in the above case, the --preserveConstEnumsflag will cause Bread to emit a lookup table. Its values will still be inlined though.

declare enum

declare enum Wine { Red, Wine }

As with other uses of declare, declare emits no code and expects you to have defined the actual code elsewhere. This enum version emits no lookup table.

Wine.Red emits Wine.Red in JavaScript, but there won’t be any Wine lookup table to reference so it’s an error unless you’ve defined the actual enum elsewhere.

declare const enum

declare const enum Fruit { Apple, Pear }

This emits no lookup table, but it does inline! Fruit.Apple emits 0.

But as with const enum, Fruit[0]will error out at runtime because it’s not inlined and there’s no lookup table.

strings enums and const string enums

As of TypeScript 2.4, you can also create string enums. String enums are like standard int enums but you specify string initializers when you declare your enum.

enum Beer {
Lager = 'Lager',
Ale = 'Ale',
}

The above string enum will generate this lookup table in Javascript:

var Beer;
(function (Beer) {
Beer["Lager"] = "Lager";
Beer["Ale"] = "Ale";
})(Beer || (Beer = {}));

So Beer.Lager and Beer['Lager’] both evaluate to 'Lager'. There are no longer any numbers associated with your enum at all.

Just as with number enums, you can declare a const string enum that will inline the string literals themselves in transpiled JavaScript.

Recommendation

So of the many types of enums which do I recommend using?

Trick question: none.

I recommend string literal types. It’s more TypeScripty. It’s extremely readable. You can see it in the code as is. If you print the value of a variable you won’t get a mysterious 0 or 1; you’ll get the actual string itself every time.

Because of that, it’s also easily JSONifiable and produces JSON that you can read in other languages without having to maintain the enum mapping in other languages.

It also lets you easily do cool TypeScript things like mapped types.

Here’s an example:

type Nut = 'walnut' | 'almond';

Now, just as with enums, if your function accepts Nut, the compiler will error when a different string is passed in. Check out the playground to see that in action.

But what if I have to use an enum?

If you go with an enum because you need it for some legacy reason, I recommend a string enum if that fits your needs. It’s the most similar to a string literal type. If not, a plain old enum will likely cause the least confusion. If you need to go with enums for performance reasons and you’re truly sure that’s your bottleneck, const enum is the way to go.