Alternatives to TypeScript enum

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:

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:

Cons:

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:

Pros of the plain objects approach:

Cons:

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

Views are my own