TypeScript Enums Cheat Sheet
Covers numeric and string enums, const enums, reverse mapping, and modern alternatives like literal unions and the as const pattern.
Numeric & String Enums
The two core enum flavors and how their values differ.
enum Direction { Up, Down, Left, Right } // numeric: 0, 1, 2, 3console.log(Direction.Up); // 0console.log(Direction[0]); // "Up" - reverse mapping (numeric enums only)enum Status { // custom numeric start Active = 1, Inactive, // 2 (auto-increments) Pending, // 3}enum Role { // string enum - no reverse mapping, more debuggable Admin = 'ADMIN', User = 'USER', Guest = 'GUEST',}console.log(Role.Admin); // "ADMIN"
Const Enums
Fully inlined enums that avoid emitting a runtime object.
const enum Direction2 { Up, Down, Left, Right }let d = Direction2.Up; // inlined to `let d = 0;` at compile time, no object emitted// Cannot use computed members or reverse mapping with const enum// Cannot be used with isolatedModules (each file compiled independently)
Union Types & 'as const' as Alternatives
Common patterns teams use instead of enums.
// Many teams prefer literal union types over enumstype Role2 = 'ADMIN' | 'USER' | 'GUEST';// 'as const' object pattern - good tree-shaking, no separate enum typeconst Role3 = { Admin: 'ADMIN', User: 'USER', Guest: 'GUEST',} as const;type Role3Type = typeof Role3[keyof typeof Role3]; // 'ADMIN' | 'USER' | 'GUEST'
Key Facts
Behaviors worth knowing before choosing an enum style.
- Numeric enum reverse mapping- Numeric enums generate both Name-to-value and value-to-Name lookups on the compiled object
- String enums- No reverse mapping; each member must be initialized explicitly
- Heterogeneous enums- Mixing string and numeric members is allowed but discouraged
- const enum- Fully inlined at compile time; produces no runtime object, smaller bundle
- Computed members- Numeric enum members can use expressions, but only the first member can be uninitialized after one
- enum as a type- The enum name doubles as a type: function f(r: Role) {}
Bitwise Flag Enums
Use power-of-two numeric enum members to combine multiple flags into a single value.
enum Permission { None = 0, Read = 1 << 0, // 1 Write = 1 << 1, // 2 Execute = 1 << 2, // 4 Delete = 1 << 3, // 8}const userPerms = Permission.Read | Permission.Write; // 3 - combine with ORfunction hasPermission(perms: Permission, flag: Permission): boolean { return (perms & flag) === flag; // check with AND}hasPermission(userPerms, Permission.Write); // truehasPermission(userPerms, Permission.Delete); // falseconst revoked = userPerms & ~Permission.Write; // remove a flag with AND-NOT
Iterating Enum Members Safely
Numeric and string enums compile differently, so iteration needs to account for reverse-mapping keys.
enum Status { Active = 1, Inactive, Pending }// Numeric enum object also contains reverse-mapping keys ("1", "2", "3"),// so filter them out before iteratingconst names = Object.keys(Status).filter((k) => Number.isNaN(Number(k)));// ["Active", "Inactive", "Pending"]const values = Object.values(Status).filter((v) => typeof v === 'number');// [1, 2, 3]enum Role { Admin = 'ADMIN', User = 'USER' }// String enums have no reverse mapping, so this is simplerObject.values(Role).forEach((r) => console.log(r)); // 'ADMIN', 'USER'
Attaching Static Helpers via Namespace Merging
Declaration-merge a namespace with an enum to bundle related utility functions under the same name.
enum Direction { Up, Down, Left, Right }namespace Direction { export function opposite(d: Direction): Direction { switch (d) { case Direction.Up: return Direction.Down; case Direction.Down: return Direction.Up; case Direction.Left: return Direction.Right; case Direction.Right: return Direction.Left; } } export const all: Direction[] = [Direction.Up, Direction.Down, Direction.Left, Direction.Right];}Direction.opposite(Direction.Up); // Direction.DownDirection.all.length; // 4
Exhaustiveness Checking with an Enum Switch
Force a compile error whenever a new enum member isn't handled by every switch statement.
enum PaymentMethod { Card, Cash, Crypto }function fee(method: PaymentMethod): number { switch (method) { case PaymentMethod.Card: return 0.03; case PaymentMethod.Cash: return 0; case PaymentMethod.Crypto: return 0.01; default: { const _exhaustive: never = method; // errors if a new member is added and unhandled throw new Error(`Unhandled payment method: ${_exhaustive}`); } }}
Advanced Enum Gotchas
Lesser-known behaviors that trip up teams using enums at scale.
- const enum + isolatedModules- Banned under isolatedModules/verbatimModuleSyntax because each file is transpiled independently and can't see the inlined values from other files
- declare const enum- Ambient const enums assume the value exists elsewhere at runtime; misusing them with --isolatedModules causes a hard error
- Enum merging rules- Only enums of the same kind (all-numeric or all-const) can merge across declarations; mixing throws a compile error
- keyof typeof enum- `type Keys = keyof typeof Role` yields the string literal union of member names, useful for prop validation
- Enums across .d.ts boundaries- A const enum consumed from a separately compiled .d.ts (e.g. a published npm package) can silently break if the source enum changes without republishing
- Enum member as a type- Individual members are also types: `type Up = Direction.Up` narrows to exactly that literal
- String enum + template literal- String enum members can be used inside template literal types: type Route = `/api/${Role}`
Prefer string enums or an 'as const' object over numeric enums for anything serialized (API payloads, database values, logs) - numeric enum values are position-dependent, so inserting a new member in the middle silently shifts every downstream integer value.