What is an enum in TypeScript
Enum in TypeScript is a set of named constants that compiles to a JavaScript object, giving you type safety instead of raw strings or numbers scattered across your code.
Theory
TL;DR
- An enum is like a contact list: you call people by name, not by phone number. TypeScript enforces that only valid names are used.
- Numeric enums auto-increment (0, 1, 2...) and support reverse mapping. String enums require explicit values and do not.
const enuminlines values at compile time - no runtime object is created.- Use enums for fixed sets like statuses, roles, or directions. For external API data, union types fit better.
Quick example
enum Status {
Pending = 0,
InProgress = 1,
Done = 2
}
const task: Status = Status.Done; // type-safe
console.log(task); // 2
console.log(Status[2]); // 'Done' (reverse mapping)
// String enum
enum Role {
Admin = 'admin',
User = 'user'
}
const userRole: Role = Role.Admin;
console.log(userRole); // 'admin' (no reverse mapping)Numeric enums support reverse mapping - Status[2] gives 'Done'. String enums do not. That single difference drives most of the trade-offs below.
Key difference
Numeric enums produce a bidirectional object in JavaScript: name-to-value and value-to-name. So Status[2] returns 'Done'. String enums only map names to values, which makes them predictable with JSON serialization and APIs. The trade-off is giving up the reverse lookup.
When to use
- Numeric enum: game states, HTTP status codes, database IDs where you might need reverse lookup
- String enum: user roles, permission levels, event types that get serialized to JSON or sent over APIs
- const enum: performance-sensitive paths where values should be inlined and no runtime object is needed
- Union type instead: when values come from external sources, or you want a compile-time-only solution
How TypeScript compiles enums
TypeScript compiles a numeric enum to a JavaScript object with two sets of entries: Status['Pending'] = 0 and Status[0] = 'Pending'. String enums only get the forward mapping. const enum is different - the compiler replaces every reference with the literal value and generates no object at all. So const myColor = Color.Red becomes const myColor = 0 in the output.
Common mistakes
Mistake 1: Expecting Object.values() on a numeric enum to return just numbers
enum Priority {
Low, // 0
Medium, // 1
High // 2
}
console.log(Object.values(Priority));
// [0, 1, 2, 'Low', 'Medium', 'High'] - both directions are includedThe numeric enum object stores both { 0: 'Low', Low: 0, ... }, so Object.values() returns six items, not three. Filter by type, or switch to a string enum.
Mistake 2: Calling Object.values() on a const enum at runtime
const enum Status {
Active = 'active',
Inactive = 'inactive'
}
const list = Object.values(Status); // Runtime error: Status is undefinedconst enum has no runtime object. If you need to iterate the values, use a regular enum.
Mistake 3: Mixing numeric and string values in one enum
// Avoid: reverse mapping breaks for numeric members
enum Mixed {
Success = 1,
Error = 'error'
}Pick one type. Mixed enums break reverse mapping and are hard to read.
Mistake 4: Using enums for external API data
enum Role {
Admin = 'Admin', // capital A
User = 'User'
}
const fromApi = { role: 'admin' }; // API returns lowercase
fromApi.role === Role.Admin; // false - never matchesMost "enum not working with API" bugs come down to case mismatch exactly like this. For data from APIs, union types (type Role = 'admin' | 'user') fit better. Or normalize the incoming value before comparing.
Real-world usage
- React/Redux: action types (
enum ActionType { FETCH_START = 'FETCH_START' }) - Express/Node.js: HTTP status codes, error types, DB connection states
- NestJS: role-based access control decorators use enums for permission levels
- GraphQL: enum types in the schema map directly to TypeScript enums
- Games: Phaser and Babylon.js use enums for game states, input keys, collision types
Follow-up questions
Q: What is the difference between an enum and a union type like type Status = 'pending' | 'done'?
A: Enums create a runtime object and give you a named group you can reference across files. Union types are compile-time only, lighter in the bundle, and better for values that come from external sources. Use enums when you need iteration or reverse mapping. Use union types for simpler cases.
Q: Why use const enum instead of a regular enum?
A: const enum replaces every member reference with its literal value at compile time. No object ends up in the bundle. The downside: you cannot call Object.values(), use reverse mapping, or do any runtime reflection on it.
Q: You have a numeric enum with values 1, 5, and 10. What does Enum[3] return?
A: undefined. Reverse mapping only exists for the exact numeric values defined in the enum. Gaps in the sequence have no entries in the JavaScript object. This is a real trap when using numeric enums to convert database IDs back to names - you get undefined with no error thrown.
Q: How do you safely check if a value from an API belongs to a string enum?
A: Object.values(MyEnum).includes(apiValue as MyEnum). This validates at runtime that the incoming string is one of the allowed values before you treat it as a typed enum member.
Examples
Basic: numeric and string enums compared
enum HttpStatus {
OK = 200,
BadRequest = 400,
NotFound = 404
}
// Reverse mapping works for numeric enums
const code = 404;
console.log(HttpStatus[code]); // 'NotFound'
enum OrderStatus {
Pending = 'pending',
Shipped = 'shipped',
Delivered = 'delivered'
}
// String enum: readable values, clean JSON output
const status: OrderStatus = OrderStatus.Shipped;
console.log(JSON.stringify({ status })); // {"status":"shipped"}HttpStatus[404] returns 'NotFound' because numeric enums store reverse entries. OrderStatus has no reverse mapping but the string values serialize cleanly to JSON.
Intermediate: type-safe order processing in Express
enum OrderStatus {
Pending = 'pending',
Processing = 'processing',
Shipped = 'shipped',
Delivered = 'delivered'
}
function handleOrder(status: OrderStatus): string {
if (status === OrderStatus.Shipped) {
return 'Sending shipping notification';
}
// This fails at compile time - TypeScript catches it before runtime:
// if (status === OrderStatus.Cancelled) { } // Error: Property 'Cancelled' does not exist
return 'No action needed';
}
handleOrder(OrderStatus.Shipped); // OK
handleOrder('shipped'); // Error: Argument of type 'string' is not assignable to type 'OrderStatus'Passing the raw string 'shipped' fails even though the value matches, because TypeScript expects OrderStatus, not string. That is the whole point of using enums.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.