Enums
An enum (enumeration) lets you define a set of named constants. Instead of scattering magic strings or numbers through your code, you group related values under a single name and refer to them symbolically.
TypeScript provides three flavours of enum, each with different trade-offs.
Numeric Enums
Numeric enums are the default. Each member is assigned an integer, starting from 0 unless you
override the start value.
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
console.log(Direction.Up); // 0
console.log(Direction.Right); // 3
// Custom start value — subsequent members auto-increment
enum StatusCode {
Ok = 200,
Created = 201,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404,
ServerError = 500,
}
function handleStatus(code: StatusCode) {
if (code === StatusCode.Ok) {
console.log('Success');
}
}Reverse Mapping
Numeric enums have a unique feature: reverse mapping. TypeScript generates a two-way mapping so you can look up a member's name from its numeric value at runtime.
enum Direction {
Up,
Down,
Left,
Right,
}
console.log(Direction[0]); // "Up"
console.log(Direction['Up']); // 0
console.log(Direction[Direction.Left]); // "Left"
// The compiled JavaScript object looks like:
// { Up: 0, Down: 1, Left: 2, Right: 3,
// 0: 'Up', 1: 'Down', 2: 'Left', 3: 'Right' }Direction[value] to see a human-readable name instead of a raw integer.String Enums
String enums require each member to be explicitly initialised with a string value. They have no reverse mapping but their values are human-readable at runtime, which makes debugging and logging much easier.
enum LogLevel {
Debug = 'DEBUG',
Info = 'INFO',
Warning = 'WARNING',
Error = 'ERROR',
}
function log(level: LogLevel, message: string) {
console.log(`[${level}] ${message}`);
}
log(LogLevel.Error, 'Something went wrong');
// Prints: [ERROR] Something went wrong
// String enum values are readable in network requests, JSON, logs
const payload = {
level: LogLevel.Info,
message: 'User logged in',
};
// payload.level === 'INFO' (not 2 or some opaque number)Const Enums
A const enum is like a regular enum but TypeScript inlines the values at every use site and
emits no JavaScript object. This eliminates the runtime object entirely, reducing bundle size.
const enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
function move(dir: Direction) {
console.log(dir);
}
move(Direction.Up);
// Compiled to: move('UP') — the enum object does not exist at runtimeconst enum has sharp edges. It does not work across module boundaries when compiled with isolatedModules: true (the default in Vite, esbuild, and SWC). It also cannot be used in .d.ts files consumed by external packages. Prefer regular enums or as const objects unless you have measured a bundle-size need.Real-World Examples
Here are patterns that appear regularly in production TypeScript codebases.
// Application route names (string enum)
enum AppRoute {
Home = '/',
Dashboard = '/dashboard',
Settings = '/settings',
Login = '/login',
}
// User roles
enum UserRole {
Guest = 'GUEST',
Member = 'MEMBER',
Admin = 'ADMIN',
}
function canAccess(role: UserRole, route: AppRoute): boolean {
if (route === AppRoute.Dashboard && role === UserRole.Guest) {
return false;
}
return true;
}
// Keyboard key codes (numeric enum)
enum Key {
Enter = 13,
Escape = 27,
Space = 32,
ArrowUp = 38,
}
document.addEventListener('keydown', (e) => {
if (e.keyCode === Key.Escape) {
closeModal();
}
});When Enums Are Problematic
Enums have several well-known footguns that have led parts of the TypeScript community to avoid them
in favour of as const objects.
Numeric enums accept any number by default — TypeScript does not prevent out-of-range values.
const enumis incompatible withisolatedModules(used by Vite, esbuild, SWC, Babel) — it causes build errors in most modern toolchains.Enums produce real JavaScript objects, which increases bundle size and can confuse tree-shakers.
Numeric enum values are opaque in logs and network payloads — you see
2, not"WARNING".Merging or extending an enum is not possible without re-declaring it.
enum Direction {
Up = 0,
Down,
Left,
Right,
}
// TypeScript allows this — any number is assignable to a numeric enum
const d: Direction = 999; // No error!
// String enums are stricter
enum Color {
Red = 'RED',
Green = 'GREEN',
Blue = 'BLUE',
}
const c: Color = 'RED'; // Error: string is not assignable to type 'Color'
const c2: Color = Color.Red; // ✓The Alternative: as const Objects
Many teams use as const objects paired with a derived union type as a drop-in replacement for enums.
This approach avoids the footguns while preserving most of the benefits.
// Instead of an enum...
const Direction = {
Up: 'UP',
Down: 'DOWN',
Left: 'LEFT',
Right: 'RIGHT',
} as const;
// Derive the union of values
type Direction = typeof Direction[keyof typeof Direction];
// => 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'
function move(dir: Direction) {
console.log(dir);
}
move(Direction.Up); // ✓
move('UP'); // ✓ — string literals are also accepted
move('DIAGONAL'); // Error
// No generated JavaScript object beyond a plain object literal
// Works with isolatedModules, tree-shakeable, debuggableComparison: Enum vs const Object
Feature | enum | as const object |
|---|---|---|
Runtime object | Yes (bi-directional for numeric) | Yes (plain object) |
Bundle size | Larger (enum boilerplate) | Smaller (plain object) |
isolatedModules | const enum breaks it | Always works |
String values in logs | Only for string enums | Always |
Extend / merge | Not possible | Spread and reassign |
Type safety | Strict for string enums | Strict with derived union |
Accepts raw strings | No (string enum) | Yes (literals allowed) |
Reverse mapping | Yes (numeric enum only) | No |
Heterogeneous Enums (Avoid)
TypeScript technically allows mixing string and numeric members in the same enum. This is almost always a mistake — avoid it.
// Technically valid — practically confusing
enum Mixed {
No = 0,
Yes = 'YES',
}
// The inconsistency makes code harder to reason about.
// Pick one: all numeric or all string.Enums in switch Statements
Enums pair naturally with switch statements. When you use exhaustiveness checking (see the
Exhaustiveness page), TypeScript will warn you if you forget to handle a new enum member.
enum TrafficLight {
Red = 'RED',
Yellow = 'YELLOW',
Green = 'GREEN',
}
function getInstruction(light: TrafficLight): string {
switch (light) {
case TrafficLight.Red: return 'Stop';
case TrafficLight.Yellow: return 'Prepare to stop';
case TrafficLight.Green: return 'Go';
default:
// Exhaustiveness check — this line is unreachable if all cases are covered
const _exhaustive: never = light;
return _exhaustive;
}
}Quick Reference
Numeric enums auto-increment from 0 (or a custom start value)
Numeric enums have reverse mapping —
Direction[0]returns"Up"String enums require explicit string values and have no reverse mapping
const enuminlines values at compile time — incompatible with isolatedModulesNumeric enums accept any number — string enums are strictly typed
Consider
as constobjects + derived union as a modern, safer alternativeAvoid heterogeneous enums (mixed string and numeric members)