Alternatives to TypeScript enum

TypeScript, like many programming languages provides enums, which enable you to enumerate the values a type may have.

TS enums look like this:

enum Direction {
UP,
DOWN
}
function move(direction: Direction) {}move(Direction.UP);

I’ll go into:

  • Alternatives to enums: string unions and object literals
  • How enum is terrible

Note: TS is a fantastic programming language, and enum may have been a good choice back in 2011 when good alternatives did not exist.

Alternative 1: String Unions

This solution uses a union of string literal types.

type Direction = "UP" | "DOWN";
function move(direction: Direction) {
switch (direction) {
case "UP":
// ...
}
// ...
}

TS gives great autocompletes for these, and gives helpful error messages if you make type errors.

Pros of the approach:

  • Both the definition and use sites are readable and no-boilerplate

Cons:

  • The JS output looks the same as the TS, except without the type declaration and : Directionannotation. Which is great! But it's not the kind of JS a normal JS dev would write. The next solution is classic JS style, but is a bit clunky in TS.

Note: number literal types provide an alternative to numeric enums: type ENV_ID = 1

Alternative 2: Object Literals

This solution is just standard JS pseudo-enums with some quirky annotations:

const DIRECTIONS = {
UP: "UP",
DOWN: "DOWN"
} as const;
type DIRECTIONS = typeof DIRECTIONS[keyof typeof DIRECTIONS];

It works!:

const d1: DIRECTIONS = DIRECTIONS.UP; // OK
const d1: DIRECTIONS = "UP"; // OK
const d2: DIRECTIONS = "PLASTICS"; // Error
const d3: DIRECTIONS = // Autocompletes: "UP", "DOWN"

The solution relies on the following advanced TS features:

  • const assertions to not lose the information about the specific strings in the object.
  • typeof to get the type of a variable
  • keyof to get a union type for the keys of the object.
  • union types
  • lookup types to index into an object type
  • The distributivity of union types: ObjType["A" | "B"] is the same as ObjType["A"] | ObjType["B"]
  • the trick that a type aliases and constants live in separate worlds, so can have the same spelling.

Pros of the plain objects approach:

  • This solution is the most straightforward I have seen for cases for converting existing JS files to TS.
  • Compiles to really normal JS

Cons:

  • The reason JS devs write things like DIRECTIONS.UP instead of just "UP" is so they don't end up with semi-hardcoded strings everywhere. But TS's autocomplete will actually fight to mitigate this advantage. Note the autocomplete in the example above is not DIRECTIONS.UP but the plain string "UP".
  • The solution might induce cargo-culting or scare people away from TypeScript.

How enum is terrible (now that there are better alternatives)

Issue 1: The transpile output of enum is super weird, which builds lock-in to TS and impairs debuggability:

enum Direction {
Up,
Down
}

outputs:

var Direction;
(function (Direction) {
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Down"] = 1] = "Down";
})(Direction || (Direction = {}));

Issue 2: enum values default to numbers (not strings), which is also bad for debuggability:

console.log(Direction.Up); // 0

Issue 3: Using TS runtime features means you may have to deal with breaking changes if JS evolves a similar feature with different semantics. There is already a conflicting proposal for enum in JS. This sort of thing has happened before:

Issue 4: The assignability rules don’t make sense:

enum A {
foo = 0;
}
// should error, but does not error
const test: A.foo = 1729;

Issue 5: Declaration merging is pretty scary:

enum Shape {
Line = 0,
}
enum Shape {
Dot = 0, // permitted
}
const x: Shape.Dot = Shape.Line; // No error!
console.log(Shape[0]); // logs "Dot", because we added `Dot` last!

TypeScript, the Line is a Dot to you!

Noting again: TS is a fantastic programming language, and enum may have been a good choice back in 2011 when better alternatives did not exist.

Related Reading

These pro-enum posts to provide an alternative perspective:

--

--

--

Views are my own

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Max Heiber

Max Heiber

Views are my own

More from Medium

Debugging TypeScript using Replay Node

Script Of Typescript

What’re the Interfaces in TypeScript, and What’s the Difference Between Type Aliases and Interfaces?

How to pass a class to a function in TypeScript