Enums in TypeScript Explained
An enum is a way to define a set of named constants in TypeScript. Learn numeric enums, string enums, const enums, and when to use enums versus union types.
Enums in TypeScript let you define a group of named constants under one name. Instead of passing around raw strings or numbers like "admin" or 1, you give each value a meaningful name and use that name throughout your code.
Here is a basic enum for user roles:
enum Role {
Admin,
Editor,
Viewer,
}This declaration creates three named constants grouped under Role, each automatically assigned a number behind the scenes. You can now use Role.Admin, Role.Editor, and Role.Viewer anywhere a value is needed:
function getPermissions(role: Role): string[] {
if (role === Role.Admin) {
return ["read", "write", "delete"];
}
return ["read"];
}
console.log(getPermissions(Role.Admin)); // ["read", "write", "delete"]
console.log(getPermissions(Role.Viewer)); // ["read"]The enum replaces magic values with named constants. The code is easier to read, and annotating the role parameter with Role means TypeScript only accepts a valid member, the same way it checks any other type annotation.
Enums are one of the few TypeScript features that produce runtime code. Unlike type annotations and interfaces, which disappear during compilation, a regular enum becomes a JavaScript object.
Numeric Enums
Numeric enums are the default form, and the Role enum above is already one. When you list members without assigning any values, TypeScript assigns auto-incrementing numbers starting from 0 in the order the members appear:
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
console.log(Direction.Up); // 0
console.log(Direction.Right); // 3Assigning a starting value changes the whole sequence: TypeScript still auto-increments each following member, but it counts up from whatever number you gave the first one instead of zero:
enum Status {
Pending = 1,
Active, // 2
Closed, // 3
}
console.log(Status.Pending); // 1
console.log(Status.Active); // 2Every member can also take its own specific value instead of relying on auto-increment at all, which is common when the numbers need to match an external system:
enum HttpStatus {
OK = 200,
Created = 201,
NotFound = 404,
ServerError = 500,
}Reverse Mapping
Numeric enums have a unique feature called reverse mapping. You can look up the name of an enum member from its numeric value:
enum Color {
Red,
Green,
Blue,
}
console.log(Color.Red); // 0
console.log(Color[0]); // "Red"
console.log(Color[1]); // "Green"This works because TypeScript generates a JavaScript object that maps both ways: name to value and value to name. The compiled JavaScript looks like this:
var Color;
(function (Color) {
Color[Color["Red"] = 0] = "Red";
Color[Color["Green"] = 1] = "Green";
Color[Color["Blue"] = 2] = "Blue";
})(Color || (Color = {}));Reverse mapping only works with numeric enums. String enums do not support it.
String Enums
String enums give each member an explicit string value instead of an auto-incrementing number. They read the same way in code, but they are far more readable in logs and debug output:
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
console.log(Direction.Up); // "UP"Every member must have a value. You cannot mix auto-incrementing numbers with strings in the same enum. TypeScript requires all members to be consistently either numeric or string-valued.
String enums are easier to debug because the runtime value is descriptive. Compare the logged output for each kind:
enum NumericStatus { Active, Inactive }
enum StringStatus { Active = "ACTIVE", Inactive = "INACTIVE" }
console.log(NumericStatus.Active); // 0 (not meaningful in logs)
console.log(StringStatus.Active); // "ACTIVE" (immediately understandable)A function parameter typed as a string enum also prevents accidental assignment of unrelated strings, even ones that look plausible at a glance. The compiler treats the enum member and its matching string literal as interchangeable, but rejects anything that does not exactly match a defined member:
function setStatus(status: StringStatus) {
console.log(status);
}
setStatus(StringStatus.Active); // ok
setStatus("ACTIVE"); // ok: the literal matches
setStatus("active"); // Error: '"active"' is not assignable
setStatus("UNKNOWN"); // Error: not a memberConst Enums
A const enum is removed entirely during compilation. Each usage is replaced with the literal value, producing zero runtime code:
const enum Size {
Small = "sm",
Medium = "md",
Large = "lg",
}
const buttonSize = Size.Medium;After compilation, the JavaScript output keeps only the final value and drops every trace of the enum declaration that originally produced it:
const buttonSize = "md";The enum object is gone. Size.Medium was inlined to "md" during compilation. This is ideal for values that only exist as types and never need to be iterated or looked up at runtime.
Const enums do not work with every build tool. Bundlers that compile each file in isolation, such as esbuild, SWC, and most modern Vite or Next.js setups, cannot see across files to inline the value, so they run with the isolatedModules compiler flag and reject const enums outright. For more on compiler flags like this one, see strict mode in TypeScript. Check your build tool before relying on const enums.
A const enum cannot be used where a runtime object is expected. You cannot pass it to a function that expects an object, and you cannot use reverse mapping:
const enum Status { Active, Inactive }
console.log(Status[0]); // Error: const enums can only be used in property accessesEnums vs Union Types
Many developers prefer union types over enums for new code. Both approaches let you define a set of allowed values, but they have different trade-offs.
// Enum approach
enum Role {
Admin = "admin",
Editor = "editor",
Viewer = "viewer",
}
// Union type approach
type Role = "admin" | "editor" | "viewer";The table below compares the two approaches:
| Enum | Union Type | |
|---|---|---|
| Runtime cost | Regular: generates object. Const: zero. | Zero (type only) |
| Debug output | String enums are readable; numeric enums are numbers | The string itself |
| Reverse mapping | Numeric enums only | Not possible |
| Iterate over values | Yes (regular enums) | No (type does not exist at runtime) |
| Autocomplete | Yes, after typing the enum name | Yes, with string literal union |
| Extra import needed | Yes | No (just the type) |
In general, prefer union types for new TypeScript code. They are simpler, produce no runtime output, and integrate naturally with the type system. Use enums when you need a runtime object that groups related constants, when reverse mapping is useful, or when you are working in an existing codebase that uses enums.
For more on union types, see union types in TypeScript.
Common Mistakes
Mixing Numeric and String Members
enum Mixed {
Yes = "YES",
No,
}Once a member has an explicit string value, TypeScript can no longer auto-increment the next one the way it does in a purely numeric enum, so the member after it needs its own explicit value too:
Enum member must have an initializer.Heterogeneous enums are technically legal in TypeScript as long as every member has an explicit value, but they are confusing to read and rarely useful. Keep enums consistently numeric or consistently string-valued.
Expecting Const Enums to Work Everywhere
const enum Colors { Red, Green, Blue }
// Error: const enums cannot be used as objects
Object.keys(Colors);Const enums are inlined and do not exist at runtime. If you need to iterate over enum values, use a regular enum instead.
Using Numeric Enums for Public API Values
enum Status { Active, Inactive }
// API response: { "status": 0 }
// Was 0 Active or Inactive? The receiver cannot tell without the enum definition.Numeric enums are not self-describing in JSON, logs, or API responses. Use string enums or union types when the value needs to be understood outside your TypeScript codebase.
Overusing Enums for Simple Constants
// Overkill: an enum for a single toggle
enum DebugMode { On, Off }A simple boolean or a union of two strings would be simpler. Use enums when you have three or more related constants that belong together under one name.
Rune AI
Key Insights
- An enum is a set of named constants grouped under one name.
- Numeric enums auto-increment from 0 and support reverse mapping.
- String enums produce readable values at runtime and are easier to debug.
- Const enums are inlined at compile time and produce zero runtime code.
- Prefer union types for most cases; use enums when you need a runtime value or reverse mapping.
Frequently Asked Questions
Should I use enums or union types in TypeScript?
Do TypeScript enums exist at runtime?
Can I use computed values in an enum?
Conclusion
Enums give you a way to name a set of related constants so your code is more readable than raw numbers or strings. Numeric enums auto-increment, string enums are debuggable, and const enums disappear at compile time for zero runtime cost. For most new TypeScript code, union types are the simpler and more idiomatic choice. Reach for enums when you need a runtime object that groups related constants with meaningful names, or when you are working in a codebase that already uses them.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.