TypeScript Enums — Complete Guide with Examples
In this tutorial, you will learn about TypeScript Enums. We cover key concepts, practical examples, and best practices to help you master this topic.
TypeScript enums let you define a set of named constants with automatic numbering, reverse mapping, and runtime presence — offering a structured alternative to magic strings and numbers throughout your codebase.
What You'll Learn
- Numeric enums and auto-incrementing
- String enums and their advantages
constenums for zero-cost abstraction- Reverse mapping in numeric enums
- Enum vs union types — which to choose
Why It Matters
Spreadsheet-style constants — STATUS_ACTIVE = 1, STATUS_INACTIVE = 2 — are error-prone and unreadable. Enums give them proper names, autocompletion, and type safety. They also serve as a bridge between TypeScript and runtime JavaScript, since enum objects exist at runtime (unlike types, which are erased).
Real-World Use
The Durga Antivirus Pro threat classification system uses a string enum for threat categories: ThreatCategory.Malware, ThreatCategory.Ransomware, etc. When a new scan result comes in, the enum ensures only valid categories are stored, displayed, and queried. The typed API prevents "category ransmware" (a typo) from ever entering the database.
Learning Path
flowchart LR A[Functions] --> B[Enums] B --> C[Type Assertions] B --> D[You Are Here] C --> E[Generics Basics] E --> F[Advanced Types]
Numeric Enums
By default, enums are numeric, starting from 0:
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
const dir = Direction.Up;
console.log(dir); // 0
console.log(Direction[0]); // "Up" — reverse mapping
Custom Starting Values
enum StatusCode {
OK = 200,
Created = 201,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404,
InternalServerError = 500,
}
console.log(StatusCode.NotFound); // 404
console.log(StatusCode[500]); // "InternalServerError"
Auto-Incrementing After a Start Value
enum ErrorCode {
Generic = 1000,
Network, // 1001
Auth, // 1002
Validation, // 1003
}
console.log(ErrorCode.Validation); // 1003
String Enums
Each member must be initialized with a string literal:
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE",
}
const color: Color = Color.Red;
console.log(color); // "RED"
function paint(car: Color): void {
console.log(`Painting car ${car}`);
}
paint(Color.Blue); // Painting car BLUE
String enums don't have reverse mapping — you can't look up "RED" to get Color.Red.
Why String Enums?
// No enum — fragile, no autocompletion
function handleStatus(status: string) {
if (status === "active") { /* ... */ }
}
handleStatus("actve"); // Typo — no error
// With enum — type-safe
enum Status {
Active = "active",
Inactive = "inactive",
Pending = "pending",
}
function handleStatus(status: Status) { /* ... */ }
handleStatus(Status.Active); // OK
handleStatus("actve"); // Error
String enums are particularly useful when interfacing with APIs that expect specific string values.
Const Enums
Regular enums generate JavaScript code at runtime. const enums are inlined during compilation:
const enum HTTPMethod {
GET = "GET",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE",
}
const method = HTTPMethod.GET;
// Compiles to: const method = "GET"; (no enum object)
Advantage: Zero runtime overhead. The enum is erased and members are inlined.
Disadvantage: You cannot use reverse mapping or iterate over a const enum. Also, if you publish a library with const enums, you must either use preserveConstEnums or document that consumers should not use isolatedModules (which breaks const enums).
Reverse Mapping
Only numeric enums have reverse mapping:
enum Status {
Active = 1,
Inactive = 2,
}
// Compiled JavaScript looks like:
// { 1: "Active", 2: "Inactive", Active: 1, Inactive: 2 }
const nameOfActive = Status[1]; // "Active"
const valueOfActive = Status.Active; // 1
This is useful for deserializing API responses:
enum Role {
Admin = "admin",
User = "user",
Guest = "guest",
}
// API returns: { role: "admin" }
function parseRole(value: string): Role | undefined {
return Object.values(Role).includes(value as Role)
? (value as Role)
: undefined;
}
Enum vs Union Types
A common design decision: when should you use an enum versus a union of literal types?
// Option A: Enum
enum Status { Active = "active", Inactive = "inactive" }
// Option B: Union type
type Status = "active" | "inactive";
| Consideration | Enum | Union |
|---|---|---|
| Runtime object | Yes (can iterate) | No (compile-time only) |
| Tree-shakable | No (unless const) | Yes (inlined) |
| Autocompletion | Yes | Yes |
| Type safety | Full | Full |
| Reverse mapping | Numeric only | No |
| File size | Adds bytes | Zero cost |
| Pattern matching | Must import enum | Works with literals |
Rule of thumb: Use a union when the values are just a small set of strings and you don't need runtime enumeration. Use an enum when you need runtime iteration, reverse mapping, or a clear namespace for related constants.
Common Mistakes
1. Assuming Const Enums Are Always Better
Const enums break with isolatedModules: true (used by many bundlers). If you're building a library, prefer regular enums or unions.
2. Expecting Reverse Mapping on String Enums
enum Color { Red = "RED" }
// Color["RED"]; // Error: no reverse mapping
3. Using Enums When a Union Would Suffice
// Overkill
enum Weekday { Mon, Tue, Wed, Thu, Fri }
const day: Weekday = Weekday.Mon;
// Simpler
type Weekday = "Mon" | "Tue" | "Wed" | "Thu" | "Fri";
const day: Weekday = "Mon";
4. Comparing Enum Members with Loose Equality
enum Status { Active = 1 }
// Bad: compares with raw number
if (someValue == 1) { }
// Good: compare with enum member
if (someValue === Status.Active) { }
5. Mutating Enum Values at Runtime
Enum objects are mutable at runtime. Don't reassign them — it defeats the purpose:
Status.Active = 99; // Works at runtime but should never be done
6. Not Using const for String Enum Object References
// If you only need the values, const enum inlines them:
const enum Color { Red = "#ff0000" }
const hex = Color.Red; // Compiles to: const hex = "#ff0000";
Practice Questions
What value does
Direction.Uphave in a numeric enum? 0 (the default starting value).Do string enums support reverse mapping? No. Only numeric enums generate reverse mapping.
What is the difference between
const enumand regularenum?const enummembers are inlined at compile time with no runtime object. Regular enums generate a runtime object with both forward and reverse mappings.When should you prefer a union type over an enum? When you only need compile-time type safety without runtime iteration, and when you want to keep the bundle size small.
Challenge: Define an enum for log levels (DEBUG, INFO, WARN, ERROR). Create a function that takes a log level and a message, and only logs messages at or above a configurable threshold level. Use numeric enum values for comparison.
FAQ
Mini Project: HTTP Request Builder
// src/http-enum.ts
enum HTTPMethod {
GET = "GET",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE",
PATCH = "PATCH",
}
enum ContentType {
JSON = "application/json",
Form = "application/x-www-form-urlencoded",
Multipart = "multipart/form-data",
Plain = "text/plain",
}
enum HTTPStatus {
OK = 200,
Created = 201,
NoContent = 204,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404,
InternalServerError = 500,
}
interface RequestConfig {
method: HTTPMethod;
url: string;
headers?: Record<string, string>;
body?: unknown;
}
function makeRequest(config: RequestConfig): Promise<{ status: HTTPStatus; data: unknown }> {
const options: RequestInit = {
method: config.method,
headers: {
"Content-Type": ContentType.JSON,
...config.headers,
},
body: config.body ? JSON.stringify(config.body) : undefined,
};
return fetch(config.url, options).then(async (res) => ({
status: res.status as HTTPStatus,
data: await res.json(),
}));
}
async function main() {
const response = await makeRequest({
method: HTTPMethod.GET,
url: "https://api.dodatech.com/scans",
});
if (response.status === HTTPStatus.OK) {
console.log("Scan data retrieved:", response.data);
} else if (response.status === HTTPStatus.Unauthorized) {
console.error("Authentication required");
}
}
console.log("Available methods:", Object.values(HTTPMethod));
// ["GET", "POST", "PUT", "DELETE", "PATCH"]
console.log("Status 200 means:", HTTPStatus[200]);
// "OK"
What's Next
Now learn about type assertions for when you know more than TypeScript:
| Lesson | Description |
|---|---|
| {{< ref "/programming-languages/typescript/06-functions" >}} | Review function typing |
| {{< ref "/programming-languages/typescript/08-type-assertions" >}} | The as keyword, type guards, non-null assertion |
| {{< ref "/programming-languages/typescript/09-generics-basics" >}} | Generic functions and type parameters |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro