Enums and Literal Types

beginner
10 min

Enums and Literal Types

Programs are full of small, fixed sets of values: order statuses, log levels, card suits. TypeScript offers two ways to model them: enums, a dedicated language feature, and literal types, which build unions out of exact values. Both are widely used, and knowing when to reach for each will make your code cleaner and safer.

Numeric Enums

An enum defines a named set of constants. By default members are numbered from 0:

typescript
enum Direction { Up, // 0 Down, // 1 Left, // 2 Right, // 3 } let move: Direction = Direction.Up;

You can set explicit values, and later members auto-increment from the last one. Numeric enums also support reverse mapping — Direction[0] returns the string "Up" — because the compiler generates a two-way lookup object at runtime.

String Enums

String enums require an explicit value per member and are generally preferred: the values are meaningful in logs, network payloads, and debuggers.

typescript
enum LogLevel { Debug = "DEBUG", Info = "INFO", Warn = "WARN", Error = "ERROR", } function log(level: LogLevel, message: string) { console.log("[" + level + "] " + message); } log(LogLevel.Warn, "Disk space low");

Unlike numeric enums, string enums have no reverse mapping and — importantly — you cannot pass a raw string where the enum is expected: log("WARN", ...) is an error. That strictness can be a feature or an annoyance depending on your API design.

const enums

Prefixing with const makes the compiler inline the values and emit no runtime object at all:

typescript
const enum Suit { Hearts, Spades, Clubs, Diamonds } const card = Suit.Spades; // compiles to: const card = 1;

They produce the smallest output but do not play well with some build tools (Babel, esbuild in isolated-module mode), so many teams avoid them.

Literal Types

A literal type is a type with exactly one value. Alone it is trivial; in unions it is powerful:

typescript
type Status = "pending" | "shipped" | "delivered"; function updateStatus(status: Status) { /* ... */ } updateStatus("shipped"); // OK, autocompleted by your editor updateStatus("shiped"); // Error: typo caught at compile time

Numeric and boolean literals work too: type Dice = 1 | 2 | 3 | 4 | 5 | 6;. Literal unions cost nothing at runtime — they are erased entirely during compilation.

The Union-from-Constant Pattern

The modern alternative to enums combines a plain object, as const, and an indexed type:

typescript
const ORDER_STATUS = { Pending: "pending", Shipped: "shipped", Delivered: "delivered", } as const; type OrderStatus = (typeof ORDER_STATUS)[keyof typeof ORDER_STATUS]; // "pending" | "shipped" | "delivered"

You get enum-like ergonomics (ORDER_STATUS.Shipped), plain-string flexibility for callers, tree-shakable output, and zero special-cased language machinery.

Enums or Literal Unions: Which Should You Use?

  • Choose literal unions for most application code: lighter, erased at compile time, friendly to JSON APIs.
  • Choose string enums when you want a nominal type callers must reference explicitly, or when your team prefers the namespace-like syntax.
  • Avoid numeric enums for new code — raw numbers in payloads are hard to read, and any number is historically assignable in older compiler versions.

Key Takeaways

  • Enums create named constant sets; string enums are the most practical variety.
  • const enum inlines values but can conflict with certain build pipelines.
  • Literal types make exact values into types; unions of literals catch typos and enable autocomplete.
  • The as const object pattern gives enum ergonomics with plain-string flexibility.

Next lesson: Classes in TypeScript — constructors, access modifiers, and inheritance.

Enums and Literal Types - TypeScript | CodeYourCraft | CodeYourCraft