Log in to GraphQL EditorGet started
TypeScript enums in 5.0 (are they still bad?)

Michał Tyszkiewicz

3/23/2023

TypeScript enums in 5.0 (are they still bad?)

TypeScript 5.0 is here and it brings a lot of changes to performance, adds decorators, and among other things enums. Enums have been much maligned for a while now and the TypeScript team has been hard at work to improve them, first with 2.0 which gave each enum member its type and then with 2.4 and the introduction of string enums. That signals that the typescript team knows that enums require a bit of work and they’re trying to improve them, so the question that remains is: are enums still bad?

If you're already familiar with enums and just want to check out what's new -> head on over to the "What's new in 5.0" section down below

Enums 101

First of all, enums are a bit of a holdover from the early days of TypeScript. They’re not a native function of JavaScript, but they have been included as a bit of a nod to C#. While Anders Hejlsberg has stated he wouldn't have included enums if he could have a do-over, the team behind TypeScript is evidently trying to get them to work.

So before we get to the changes let's outline why enums are bad, or rather why a lot of developers don't like them. To start off let's just use basic numeric enums.

Let's say we have our typical enum with pokemon types

enum PkmnTypes {
  fire,
  grass,
  water,
}

Now if we console log this we’ll get 0, 1 and 2, this is because it complies to javascript like this:

var PkmnTypes;
(function (PkmnTypes) {
  PkmnTypes[(PkmnTypes['fire'] = 0)] = 'fire';
  PkmnTypes[(PkmnTypes['grass'] = 1)] = 'grass';
  PkmnTypes[(PkmnTypes['water'] = 2)] = 'water';
})(PkmnTypes || (PkmnTypes = {}));

While that’s ugly as hell it still works. The issue is when someone adds another pokemon type into that enum (e.g. lightning for pikachu) alphabetically - right above WATER.

enum PkmnTypes {
  fire,
  grass,
  lightning,
  water,
}

Now we can console log it again to see that lightning is now 2 and water is now 3. This will most likely cause a lot of trouble.

Sure we can get around that by either adopting a policy of only appending values in enums or using exact declarations like this:

enum PkmnTypes {
  fire = 0,
  grass = 1,
  lightning = 3,
  water = 2,
}

Now the issue is if we console log this we get all the keys and values, and that's because it complies to JavaScript as mentioned above.

console.log(Object.values(PkmnTypes))

[LOG]: ["fire", "grass", "water", "lightning", 0, 1, 3, 2]
console.log(PkmnTypes)

[LOG]: {
  "0": "fire",
  "1": "grass",
  "2": "water",
  "3": "lightning",
  "fire": 0,
  "grass": 1,
  "lightning": 3,
  "water": 2
}

There's also a weird interaction with functions. Let's say we want to assign our pokemon types, it will work as intended using both the number and the name of the type, the issue is that TypeScript allows any number here (even those outside the scope of the enum) without throwing errors, which makes it not type-safe at all.

function assignPkmnType(pkmntype: PkmnTypes): void {}

assignPkmnType(1);

assignPkmnType(PkmnTypes.fire);

assignPkmnType(999);

All of this can be avoided by using the aforementioned string enums:

enum PkmnTypes {
  fire = 'fire',
  grass = 'grass',
  lightning = 'lightning',
  water = 'water',
}

which complies to JavaScript as:

var PkmnTypes;
(function (PkmnTypes) {
  PkmnTypes['fire'] = 'fire';
  PkmnTypes['grass'] = 'grass';
  PkmnTypes['lightning'] = 'lightning';
  PkmnTypes['water'] = 'water';
})(PkmnTypes || (PkmnTypes = {}));

And this while still ugly will work as expected if we console log it:

console.log(Object.values(PkmnTypes))
[LOG]: ["fire", "grass", "lightning", "water"]

console.log(PkmnTypes)
[LOG]: {
  "fire": "fire",
  "grass": "grass",
  "lightning": "lightning",
  "water": "water"
}

Three issues remain though:

  • it’s still a lot of unnecessary js code
  • it still doesn't prevent u from the other pitfalls of enums
  • it makes more sense as a const at this point

The last point is really the obvious drawback here, if you were writing it from the start without thinking about an enum you'd probably just make something like this:

const PkmnTypes = {
  fire: 'fire',
  grass: 'grass',
  lightning: 'lightning',
  water: 'water',
};

all we need to do from there is to create a new type with the same name and copy over the values

const PkmnTypes = {
  fire: 'fire',
  grass: 'grass',
  lightning: 'lightning',
  water: 'water',
} as const;

type PkmnTypes = (typeof PkmnTypes)[keyof typeof PkmnTypes];

Now we can even use numbers in our object and call them in our function and get the expected result:

const PkmnTypes = {
  fire: 'fire',
  grass: 1,
  lightning: 'lightning',
  water: 'water',
} as const;

type PkmnTypes = (typeof PkmnTypes)[keyof typeof PkmnTypes];

function assignPkmnType(pkmntype: PkmnTypes): void {}

assignPkmnType(PkmnTypes.fire);
assignPkmnType(1);

Essentially we're getting everything we would from an enum, without the drawbacks of actually using an enum. Yes, you can use a const enum, but that opens another can of worms on its own and is strongly discouraged by TypeScripts documentation itself.

What's new in 5.0?

So back to the initial question - are enums still bad in 5.0? The new addition is that all enums are now union enums which means TS will now create a unique type for each computed member, regardless of what type of enum it is. What does that mean? That they are now typesafe and you can't just add any number in without getting an error, like in the example mentioned above:

enum PkmnTypes {
  fire,
  grass,
  lightning,
  water,
}
function assignPkmnType(pkmntype: PkmnTypes): void {}

assignPkmnType(1);

assignPkmnType(PkmnTypes.fire);

assignPkmnType(999);

The last line will now throw the following error: Argument of type '999' is not assignable to parameter of type 'PkmnType'.

While that’s a big step in the right direction it still leaves the other problems, and even when those get fixed the key issue of ‘why not just use a const’ will remain for those who find them entirely redundant. Regardless those who like using enums will likely really appreciate this fix.

Unions & how to avoid using enums

While we're on the subject of unions, there's another object in TypeScript that makes much more sense to use than an enum, namely a union type.

type PkmnTypes = 'fire' | 'grass' | 'lighting' | 'water';

And because it's all types it compiles to JavaScript as completely nothing, saving us the trouble with the bloated enum compiles mentioned above. We cant access these values though so if we want to do that we just simply need to remake it as a const:

const PkmnTypes = ['fire', 'grass', 'lightning', 'water'] as const;

type PkmnType = (typeof PkmnTypes)[number];

type Pkmn = {
  pkmnType: PkmnType;
};

const Charizard: Pkmn = {
  pkmnType: 'fire',
};

That's it and we can now iterate over the values however we want, by just throwing them in a list. So in short: if you don't need access to the values in your enum just use a union type, if you do just get them into a list in a const and you can do whatever you need from there, without ever bothering with enums at all.

To sum up 5.0 for enums: if for whatever reason you like using them 5.0 is an upgrade in that regard and if you don't you can ignore them altogether just like you did before. There doesn't seem to be any specific benefit to using them (that I know of) so for those that are either starting out with TypeScript or those that are experienced users that don't bother with enums this will hardly be enticing enough to start using them now.

Check out our other blogposts

GraphQL cache: using LRU cache with GraphQL Zeus
Michał Tyszkiewicz
Michał Tyszkiewicz
GraphQL cache: using LRU cache with GraphQL Zeus
1 min read
13 days ago
Unlocking the Power of React 19
Tomasz Gajda
Tomasz Gajda
Unlocking the Power of React 19
1 min read
about 2 months ago
Zeus update - GraphQL spread operator
Michał Tyszkiewicz
Michał Tyszkiewicz
Zeus update - GraphQL spread operator
1 min read
3 months ago

Ready for take-off?

Elevate your work with our editor that combines world-class visual graph, documentation and API console

Get Started with GraphQL Editor