Alternatives to TypeScript enum

Max Heiber
Feb 2 · 3 min read
enum Shape { Line = 0 }; enum Shape { Dot = 0 }; const x: Shape.Dot = Shape.Line;
enum Shape { Line = 0 }; enum Shape { Dot = 0 }; const x: Shape.Dot = Shape.Line;

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:

Max Heiber

Written by

Views are my own

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade