Skip to main content

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 enum inlines 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

typescript
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

typescript
enum Priority { Low, // 0 Medium, // 1 High // 2 } console.log(Object.values(Priority)); // [0, 1, 2, 'Low', 'Medium', 'High'] - both directions are included

The 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

typescript
const enum Status { Active = 'active', Inactive = 'inactive' } const list = Object.values(Status); // Runtime error: Status is undefined

const 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

typescript
// 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

typescript
enum Role { Admin = 'Admin', // capital A User = 'User' } const fromApi = { role: 'admin' }; // API returns lowercase fromApi.role === Role.Admin; // false - never matches

Most "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

typescript
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

typescript
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 ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?