Right, so you’ve met numeric enums. They’re fine. They get the job done. But you and I both know that debugging something because LogLevel[2] returns "WARN" feels a bit like communicating through a translator who occasionally makes things up. Enter string enums. They are, in my utterly unbiased opinion, what enums always wanted to be when they grew up.

A string enum is exactly what it sounds like: an enum where each member is explicitly initialized with a string value. No more implicit reverse mappings to magical numbers. It’s explicit, it’s clear, and it serializes to something human-readable without you having to lift a finger.

enum LogLevel {
  Error = "ERROR",
  Warn = "WARN",
  Info = "INFO",
  Debug = "DEBUG",
}

Now, LogLevel.Error is the string "ERROR". That’s it. No funny business. When you send this over the wire to another service or stash it in a database, it’s the meaningful string "ERROR", not the number 0. This is a massive win for debugging and for any system that isn’t your TypeScript compiler.

The Why: Safer Serialization and Developer Sanity

The primary superpower of string enums is their stability and clarity in serialized contexts. Imagine you have a status for a user account:

enum AccountStatus {
  Active = "ACTIVE",
  Suspended = "SUSPENDED",
  Closed = "CLOSED",
}

const userData = {
  name: "Ada Lovelace",
  status: AccountStatus.Active, // This is the string "ACTIVE"
};

// JSON.stringify(userData) gives you:
// {"name": "Ada Lovelace", "status": "ACTIVE"}

Any other system—a Python dashboard, a Java backend, a SQL query—can read that JSON and immediately understand the status. There’s no need for a lookup table or a comment explaining what 1 means. The data is self-documenting. This eliminates a whole class of bugs where numeric values get misaligned between your frontend and backend. If the backend expects the string "SUSPENDED", that’s exactly what it’s going to get.

The Catch: No Reverse Mapping (And That’s Good)

Here’s the first thing you’ll notice, and it’s a feature, not a bug: string enums don’t support reverse mapping.

const statusValue = AccountStatus.Active; // "ACTIVE"
const keyName = AccountStatus["ACTIVE"]; // ❌ Error: Element implicitly has an 'any' type...

This is the TypeScript compiler protecting you from yourself. With a numeric enum, LogLevel[2] works because the runtime enum object is a two-way street. A string enum is a one-way street: from the meaningful name (Active) to the meaningful value ("ACTIVE"). You cannot get the member name from the value. And honestly, why would you need to? If you’re parsing a string from an API and you need to convert it back to the enum type, you use a type guard or a lookup object. This is a bit more code, but it’s explicit and safe.

// The correct way to parse a string into your enum
function parseAccountStatus(status: string): AccountStatus | null {
  if (Object.values(AccountStatus).includes(status as AccountStatus)) {
    return status as AccountStatus;
  }
  return null;
}

The Rough Edge: You Gotta Type All the Values

Yes, the one minor annoyance is that you must explicitly write a string value for every single member. There’s no auto-increment for strings (what would it even do? add one to the letter ‘A’?). This is the price of explicitness. It’s a good price. Pay it willingly. The few seconds you save by not typing = "VALUE" are not worth the hours of confusion you’ll save down the line.

Best Practice: Use Them Almost Everywhere

My rule of thumb is simple: if the enum value has any meaning outside your TypeScript code—if it’s going to be stored, logged, or sent to another service—use a string enum. The only time I even consider a numeric enum is for a purely internal flag that will never, ever escape the memory of my running application. And even then, I often use string enums just for consistency. They provide a level of future-proofing and clarity that is almost always worth the extra keystrokes. They aren’t just a TypeScript feature; they’re a design decision that makes your system’s data inherently more understandable.