# In Defence of Typescript Enums

In the past few years, there’s been a growing trend of criticizing TypeScript’s `enum` feature. Videos, blog posts, and tweets call for avoiding them entirely, and many developers are now defaulting to union types or `const` objects instead. And yes—TypeScript enums do have some quirky behaviors that make them less elegant than their counterparts in other languages. But here’s the thing:

Most of the complaints about enums aren’t about the feature itself—they’re about misuse.

I’d even go as far as to say that the majority of enum pitfalls in TypeScript are caused by a fundamental misunderstanding of what an enum is meant to be in the first place.

This article isn’t just a defense of enums. It’s a reminder of what enums *are for*—and why, when used correctly, they’re still a powerful and expressive tool in TypeScript.

---

## Enums Are Meant To Be Symbolic

Let’s get one thing straight: enums are not meant to represent literal values. They’re meant to represent symbolic ones.

This distinction might sound pedantic, but it’s at the core of the misunderstanding. As [Wikipedia](https://en.wikipedia.org/wiki/Enumerated_type) puts it:

> "An enumerated type has values that are different from each other, and that can be compared and assigned, but are not specified by the programmer."

That last part is key: *not specified by the programmer*. In other words, we shouldn’t care what the underlying value of an enum is. We only care about what it *represents*.

---

## What Does "Symbolic" Really Mean?

Let’s make it concrete.

Imagine you're building a traffic light simulation. A traffic light can be red, yellow, or green. How should you represent those states in your code?

You could do:

```typescript
const RED = "red";
const YELLOW = "yellow";
const GREEN = "green";
```

Or maybe you're feeling fancier and you use hex codes:

```typescript
const RED = "#FF0000";
const YELLOW = "#FFFF00";
const GREEN = "#00FF00";
```

Or perhaps localized strings:

```typescript
const RED = "rouge";
const YELLOW = "jaune";
const GREEN = "vert";
```

None of these are wrong—but none are ideal either. Because what you’re really trying to express is the *concept* of red, yellow, and green. Not a particular string or color code or translation. Just the idea.

That’s where symbolic values shine.

---

## JavaScript Has a Built-in Symbolic Primitive

ES6 introduced the `Symbol` primitive, and it’s a perfect metaphor for this kind of use:

```typescript
const RED = Symbol("Red");
const YELLOW = Symbol("Yellow");
const GREEN = Symbol("Green");
```

Each call to `Symbol()` returns a unique value, regardless of the label. That means `Symbol("Red") !== Symbol("Red")`, unless you store it and reuse the same reference. The value itself doesn’t matter—just the identity.

This gets to the heart of how enums should behave: they should stand for something abstract, not something concrete.

---

## Where TypeScript Gets a Little Funky

Here’s where TypeScript throws a curveball: unlike many other statically typed languages, TypeScript allows you to assign actual string or number *literal values* to enum members.

```typescript
enum TrafficLight {
  Red = "red",
  Yellow = "yellow",
  Green = "green"
}
```

This feels convenient at first. You can now log enum values, serialize them into JSON, and display them in UIs directly. But this is a trap.

Why? Because you’re no longer using enums *symbolically*. You’ve turned them into a tightly-coupled representation layer. The enum value *is* the thing you're going to display, transmit, and compare externally. And that opens the door to a few major risks:

### 1\. Leaky Abstractions

By using a string value like `"red"` directly in your enum, you’re tying your logic to that representation. Now every part of your app—your UI, backend, tests—might start to depend on that `"red"` string. If you later need to localize, change the display name, or serialize differently, you’re stuck.

Example:

```typescript
enum TrafficLight {
  Red = "red",
  Yellow = "yellow",
  Green = "green"
}

function renderLight(color: TrafficLight) {
  return `<div class="light-${color}"></div>`;
}
```

At first glance, this seems fine. But what happens when marketing wants the classes to change to `"stop"`, `"caution"`, and `"go"` instead of `"red"`, `"yellow"`, and `"green"`?

You're now forced to either:

* Rename your enum values (breaking everything),
    
* Or inject a weird indirection layer to undo the coupling you introduced in the first place.
    

You’ve tightly bound your *logic layer* and *representation layer*, which makes the system harder to evolve.

### 2\. Fragile Equality

Using literal values opens up the door for subtle bugs due to implicit casting, loose comparisons, and typos.

For example:

```typescript
if (color === "Red") {
  // oops – this will never be true if your enum is "red" (lowercase)
}
```

Or worse:

```typescript
const userInput = req.body.color; // from frontend form input

if (userInput === TrafficLight.Red) {
  // works... until someone changes the frontend string casing or translation
}
```

Now your enum value is effectively part of a contract—and not a very safe one. There's no type safety guarding the string being passed around. A typo or localization mismatch breaks your logic silently.

### 3\. Enum Values Become Public API

Once you serialize your enums as strings or numbers and expose them (e.g. in JSON APIs), they become part of your public contract. Now you're stuck maintaining these exact values forever, even if they were never intended to be meaningful.

Imagine this:

```typescript
// Backend API
{
  "status": "Processing"
}
```

This might come from:

```typescript
enum OrderStatus {
  Processing = "Processing",
  Shipped = "Shipped",
  Delivered = "Delivered",
}
```

Now `Processing` is in your API contract. What happens when you want to rename it to `InProgress`? You can't—at least not without a breaking change.

Instead, the enum should have stayed symbolic:

```typescript
enum OrderStatus {
  Processing,
  Shipped,
  Delivered
}
```

And your serialization should happen in a dedicated mapping layer:

```typescript
const statusToApi = new Map<OrderStatus, string>([
  [OrderStatus.Processing, "Processing"],
  [OrderStatus.Shipped, "Shipped"],
  [OrderStatus.Delivered, "Delivered"],
]);

return {
  status: statusToApi.get(currentStatus)
};
```

This lets you keep your internal logic clean and symbolic, while exposing only the necessary representation at the edge of your system.

---

## Enums Should Be for Internal Logic Only

When used correctly, enums are great for internal logic and branching:

```typescript
enum TrafficLight {
  Red,
  Yellow,
  Green
}

const currentLight: TrafficLight = TrafficLight.Red;

const instructions = new Map<TrafficLight, string>([
  [TrafficLight.Red, "Stop"],
  [TrafficLight.Yellow, "Slow down"],
  [TrafficLight.Green, "Go"],
]);

console.log(instructions.get(currentLight));
```

Here, the enum values themselves are symbolic. They aren’t used directly in the UI, APIs, or persisted anywhere—they're just internal tags for logic flow. If you want to serialize the enum value or display it to a user, that should be an explicit translation step (like the `Map` above).

---

## The Problem With Misunderstood Criticism

I once saw someone critique TypeScript enums in a video, complaining that enums were “unusable” because they “don’t serialize cleanly.” But that’s like complaining that a hammer doesn’t write well. Of course it doesn’t—it wasn’t designed for that.

If you’re using enums as if they’re just constant strings or serialized values, then yes, they’ll bite you. But if you use them as symbolic representations of domain ideas, then they’re incredibly powerful—and semantically rich.

---

## Should You Use TypeScript Enums?

I’ll be honest: TypeScript enums *are* quirky. The `enum` keyword behaves differently than a lot of people expect, especially those coming from other languages. But abandoning them altogether is throwing the baby out with the bathwater.

The problem isn’t that TypeScript enums are bad. The problem is that we’ve forgotten what enums are for.

So let’s bring back proper enums—used symbolically, decoupled from implementation details, and grounded in good modeling.

Because sometimes, the best way to represent an idea… is with a symbol.
