Enums
Enums in TypeScript: When and How to Use Them
Enums in TypeScript can look mysterious if you come from a JavaScript background.
You might be used to juggling strings or numbers for states, and then suddenly see the enum
keyword producing objects you did not expect.
Are enums a superpower for cleaner code or just a quirky feature to ignore?
Let's walk through what they are, why they exist, and how to use them well.
Why enums matter
In real projects you often deal with a fixed set of options. Think user roles, network request states, or days of the week. Without enums you end up with “magic strings” sprinkled through the codebase which leads to typos and invalid values slipping in at runtime. Enums group these options into a single, named collection. That makes code easier to read and safer to change.
An everyday analogy helps. Imagine a coffee shop with only three cup sizes: Small, Medium, and Large. If someone tries to order an Extra Large, the system should reject it because that option does not exist. Enums give you that same guardrail in code.
Numeric vs string enums
TypeScript gives you two main flavors: numeric and string.
- Numeric enums auto assign values starting at 0 unless you set one explicitly. For example:
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
These are useful when the values themselves do not matter, only that they are distinct.
- String enums require you to assign a string to each member:
enum ResponseStatus {
Success = 'SUCCESS',
Failure = 'FAILURE',
Pending = 'PENDING',
}
String enums are easier to debug because the value carries meaning without looking up a number. Many developers favor them for readability.
Practical use cases
Enums shine in three kinds of situations:
1. Application state
Example:
enum RequestState {
Idle = 'IDLE',
Loading = 'LOADING',
Success = 'SUCCESS',
Error = 'ERROR',
}
Using this enum means your state is limited to those four valid options.
2. Status codes
With HTTP responses you can make intent clearer:
enum HttpStatus {
OK = 200,
NotFound = 404,
InternalError = 500,
}
Calling handleResponse(HttpStatus.OK)
is more obvious than handleResponse(200)
.
3. Named constants
Any configuration or set of options benefits from enums. For instance, directions in a game or difficulty levels.
Const enums
TypeScript also has const enum
.
These do not exist at runtime at all.
Instead, the compiler replaces uses with the raw values.
This makes the code smaller and faster but you lose the ability to iterate over members or inspect them.
Use const enums only inside your own project when you really want that performance edge.
How enums work at runtime
Unlike type aliases or interfaces, enums exist at runtime. TypeScript compiles them into JavaScript objects. Numeric enums create both forward and reverse mappings so you can get the name from a number, though relying on that trick is usually an anti pattern. String enums produce cleaner objects with only the key value pairs. Since enums exists at runtime they have some drawbacks. Check out this video by Matt Pocock for more details on the downsides of enums.
Enums vs union types
A common alternative is using a union of string literals:
type DirectionLiteral = 'Up' | 'Down' | 'Left' | 'Right'
This has no runtime cost and still gives you type safety.
Many in the community prefer this pattern or the as const
object approach because it stays closer to plain JavaScript.
Enums are still useful when you want an actual object to exist at runtime.
Common mistakes
Watch out for these pitfalls:
- Assuming numeric enums prevent invalid numbers. At runtime they are just numbers, so you need explicit checks if data comes from outside.
- Depending on implicit numeric values. Reordering members changes the values.
- Using reverse lookup in real code. It is fragile and confusing.
- Mixing string and numeric members in the same enum. Avoid this entirely.
- Expecting a const enum to exist at runtime. It will not.
A micro utility that often helps is writing a type guard. For example:
const isFruit = (value: string): value is Fruits =>
Object.values(Fruits).includes(value as Fruits)
This ensures an incoming string really is one of the enum's values before you trust it.
Wrap up
Enums in TypeScript are a structured way to handle sets of related constants. They improve readability and help prevent errors, but they also come with quirks. Enums are great to organize your data and reuse them across your codebase.
Use union types when you only need type safety without extra JavaScript and only have a small set of values. The key is to be intentional and pick the right tool for your context.
Have you run into any tricky enum bugs or clever enum patterns in your own projects? Share them with us! We would love to hear how you handle fixed sets of values in TypeScript.
Stay Updated
Get the latest TypeScript tips, tutorials, and course updates delivered straight to your inbox.