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.
An enum defines a named set of constants. By default members are numbered from 0:
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 require an explicit value per member and are generally preferred: the values are meaningful in logs, network payloads, and debuggers.
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.
Prefixing with const makes the compiler inline the values and emit no runtime object at all:
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.
A literal type is a type with exactly one value. Alone it is trivial; in unions it is powerful:
type Status = "pending" | "shipped" | "delivered";
function updateStatus(status: Status) { /* ... */ }
updateStatus("shipped"); // OK, autocompleted by your editor
updateStatus("shiped"); // Error: typo caught at compile timeNumeric 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 modern alternative to enums combines a plain object, as const, and an indexed type:
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.
const enum inlines values but can conflict with certain build pipelines.as const object pattern gives enum ergonomics with plain-string flexibility.Next lesson: Classes in TypeScript — constructors, access modifiers, and inheritance.