Why is enum often considered an antipattern in TS?
In short: why enum is often called an antipattern
Because
enumis "runtime magic" that hurts type safety, adds bulk to the code, and integrates worse with the JS ecosystem than alternatives likeconst enumoras constobjects.
1. enum exists both at runtime and at compile time
The main feature (and problem) of enum is
that it generates JavaScript code at compile time.
Example:
enum Direction {
Up,
Down,
Left,
Right,
}Compiles to JS as:
"use strict";
var Direction;
(function (Direction) {
Direction[(Direction["Up"] = 0)] = "Up";
Direction[(Direction["Down"] = 1)] = "Down";
Direction[(Direction["Left"] = 2)] = "Left";
Direction[(Direction["Right"] = 3)] = "Right";
})(Direction || (Direction = {}));That is, enum is not a purely type-level construct,
it generates a real object, which:
- adds extra code to the bundle,
- can behave differently than you'd expect.
2. A two-way mapping (reverse mapping) = a potential bug
TypeScript creates a two-way table:
Direction.Up === 0
Direction[0] === "Up"This is convenient, but dangerous:
enum Role { Admin, User }
const role = Role[0]; // "Admin"Values can be reversed, which creates hidden vulnerabilities and confusion.
3. Enum values are not type-safe
An enum behaves almost like an object with number | string values,
and TypeScript often does not protect against assignment errors.
Example:
enum Role { Admin, User }
const role: Role = 42; // allowed (!)This is not obvious and breaks the point of an enum,
because 42 is not an existing value,
but TS does not filter it out.
4. Mixing types (numeric + string enums)
You can write this:
enum Mixed {
Yes = "YES",
No = 0,
}Combining numbers and strings like this leads to unpredictable behavior and makes type checking harder.
5. enum does not work well with tree-shaking
Since enum compiles to a real JS object,
it cannot be removed by the bundler's optimizer (for example, Terser or esbuild).
Example:
enum Colors { Red, Blue, Green }Even if Colors is not used,
it still ends up in the bundle.
But an object with as const is removed
if it is not used:
const Colors = { Red: 0, Blue: 1, Green: 2 } as const;6. Poor compatibility with plain JS / JSON / APIs
An enum is a runtime object, and its values do not match plain strings.
Example:
enum Status { Active = "active", Inactive = "inactive" }
function setStatus(status: Status) { ... }
setStatus("active"); // Error - TS expects Status.ActiveAlthough the string "active" matches in meaning,
TypeScript requires exactly Status.Active.
This gets in the way of using an enum with data from an API.
It is better to use a string literal:
type Status = "active" | "inactive";7. Difficulties with serialization and debugging
enum Status { Active, Inactive }
JSON.stringify(Status);
// {"0":"Active","1":"Inactive","Active":0,"Inactive":1}Instead of a simple enumeration, you get a useless jumble that's hard to pull the needed values out of.
8. Enum behavior breaks the "value-level vs type-level" concept
TypeScript normally separates:
- type level (only at compile time)
- value level (at runtime)
enum mixes these two levels.
Example:
enum Fruit { Apple, Orange }
function eat(fruit: Fruit) { ... }
const a = Fruit.Apple; // a variable and a type in oneThis is "magic" that breaks TS's predictability principles.
9. An alternative: as const + typeof
A more modern and safer approach is literal objects with as const.
Example:
const Direction = {
Up: "up",
Down: "down",
Left: "left",
Right: "right",
} as const;
type Direction = typeof Direction[keyof typeof Direction];Advantages:
- Generates no JS code (only types);
- Works with APIs and JSON;
- Tree-shaking friendly;
- Does not break typing.
10. const enum - a compromise (but with its own nuances too)
If you still need an enum, it's better to use const enum:
const enum Direction {
Up,
Down,
Left,
Right,
}TypeScript inlines the values directly:
const move = Direction.Up;
// compiles to:
const move = 0;But:
- it does not work with
isolatedModules: true(for example, with Babel); - it breaks debugging (values turn into numbers);
- it can be incompatible with other tools (for example, SWC, ts-node).
11. enum breaks the idea of "a type as a contract document"
For example, when you use enum in a public API,
consumers see numeric codes rather than literal values.
This makes the type less self-documenting.
Example:
enum Status { Success, Fail }
function getStatus(): Status { ... }Returns 0 | 1, but the IDE will not hint "Success" | "Fail" -
understanding the code becomes harder.
12. Summary: when enum is actually needed
enum is justified only in rare cases:
- for compatibility with existing C#/Java code;
- when working with external libraries where an enum is part of the API;
- in specific compile-time scenarios (
const enum).
For everything else:
use a string literal union or an as const object.
Final comparison table
| Approach | Generates code? | Safe? | Convenient with APIs? | Tree-shaking | Recommended |
|---|---|---|---|---|---|
enum | Yes | Partially | No | No | No |
const enum | No (inline) | Yes | Partially | Yes | With caution |
as const object | No | Yes | Yes | Yes | Recommended |
| A union of types (`'a' | 'b'`) | No | Yes | Yes | Yes |
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.