Skip to content
Laviyx Learn

Try

TypeScript course menu

TypeScript Enums: Numeric and String Enums, and When to Use Alternatives

Learn TypeScript enums: numeric enums and auto-increment, string enums, using enums in code, reverse mapping, const enums, why many teams prefer literal unions or as const objects, and how enums behave with Node.js type stripping.

Intermediate7 min readUpdated
On this page

An enum defines a set of named constants. Enums are one of the few TypeScript features that is not just types: they produce real JavaScript objects at runtime.

Numeric enums#

TypeScript
enum Direction {
  Up,
  Down,
  Left,
  Right,
}

function move(direction: Direction): string {
  return `Moving ${Direction[direction]} (value ${direction})`;
}

console.log(move(Direction.Left));
console.log(Direction.Up, Direction.Right);
Output
Moving Left (value 2)
0 3
  • Members get numbers automatically from 0 (Up = 0, Down = 1, ...). You can set a starting value: Up = 1.
  • Numeric enums have a reverse mapping: Direction[2] is "Left".

String enums#

String enums give each member an explicit string value, which is more readable in logs and data:

TypeScript
enum OrderStatus {
  Placed = "PLACED",
  Shipped = "SHIPPED",
  Delivered = "DELIVERED",
}

function nextStatus(status: OrderStatus): OrderStatus | null {
  switch (status) {
    case OrderStatus.Placed:
      return OrderStatus.Shipped;
    case OrderStatus.Shipped:
      return OrderStatus.Delivered;
    case OrderStatus.Delivered:
      return null;
  }
}

console.log(nextStatus(OrderStatus.Placed));
console.log(Object.values(OrderStatus));
Output
SHIPPED
[ 'PLACED', 'SHIPPED', 'DELIVERED' ]

String enums have no reverse mapping.

What an enum compiles to#

Because enums exist at runtime, the compiler generates code for them. The string enum above becomes roughly:

JavaScript
var OrderStatus;
(function (OrderStatus) {
  OrderStatus["Placed"] = "PLACED";
  OrderStatus["Shipped"] = "SHIPPED";
  OrderStatus["Delivered"] = "DELIVERED";
})(OrderStatus || (OrderStatus = {}));

This matters because tools that strip types without compiling (such as running .ts files directly in Node.js) cannot handle enums, since they would need to generate this code. TypeScript's erasableSyntaxOnly compiler option reports enums and similar features for exactly this reason.

const enum declarations are inlined (replaced by their values) and generate no object, but they have their own limitations with some build tools.

Enums are nominal#

String enums only accept their own members, not equal strings:

TypeScript
enum OrderStatus {
  Placed = "PLACED",
  Shipped = "SHIPPED",
}

const s: OrderStatus = "PLACED";
Compiler error
error TS2322: Type '"PLACED"' is not assignable to type 'OrderStatus'.

This is sometimes useful (it forces use of the named constant) but awkward when values come from JSON or an API as plain strings.

Alternatives: literal unions and as const#

Many TypeScript teams prefer simpler alternatives that are pure types or plain JavaScript:

A union of string literals#

TypeScript
type OrderStatus = "placed" | "shipped" | "delivered";

function label(status: OrderStatus): string {
  return status[0].toUpperCase() + status.slice(1);
}
console.log(label("shipped"));
Output
Shipped

No runtime code, works with plain strings from APIs, and editors still auto-complete the values (Literal Types).

An as const object#

When you also want named constants and a list of values at runtime:

TypeScript
const OrderStatus = {
  Placed: "placed",
  Shipped: "shipped",
  Delivered: "delivered",
} as const;

type OrderStatus = (typeof OrderStatus)[keyof typeof OrderStatus];
// "placed" | "shipped" | "delivered"

function isDone(status: OrderStatus): boolean {
  return status === OrderStatus.Delivered;
}

console.log(isDone(OrderStatus.Shipped), isDone("delivered"), Object.values(OrderStatus));
Output
false true [ 'placed', 'shipped', 'delivered' ]

This gives OrderStatus.Shipped-style constants like an enum, accepts plain strings, and is ordinary JavaScript. The keyof typeof syntax is explained in keyof, typeof and Indexed Access.

Which to choose?#

OptionRuntime objectAccepts plain stringsWorks with type stripping
enumYesNo (string enums)No
Literal unionNoYesYes
as const object + derived typeYesYesYes

Enums are fine and common in existing codebases (and in frameworks such as Angular and NestJS). For new code, literal unions are the simplest choice, and as const objects when you need runtime values.

Practice#

Practice exercise

Rewrite this enum as an as const object with a derived Priority type, and write isUrgent(p: Priority): boolean that is true only for "high":

TypeScript
enum Priority { Low = "low", Medium = "medium", High = "high" }
Show solution
TypeScript
const Priority = {
  Low: "low",
  Medium: "medium",
  High: "high",
} as const;

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

function isUrgent(p: Priority): boolean {
  return p === Priority.High;
}

console.log(isUrgent(Priority.Low), isUrgent("high"));
Output
false true

Key takeaway#

Finished this lesson?