Enums in TypeScript: A Friendly Guide to Named Constant Sets

Learn how enums in TypeScript group related constants under readable names. We cover numeric vs string enums, when they help, and the union-of-literals alternative.

Published September 20, 20269 min readBy ACY Partner Indonesia
Enums in TypeScript cover illustration
300 × 250Ad Space AvailablePlace your ad here

Imagine you are reading code and you stumble on a line like if (order.status === 2). What does 2 mean? Is it “shipped”? “cancelled”? “waiting for payment”? You have no idea unless you go hunting through the rest of the file. Numbers and loose strings scattered around your code are a quiet source of bugs, because they carry no meaning on their own.

Enums are TypeScript’s answer to that problem. An enum (short for “enumeration”) is a named set of related constants. Instead of writing 2, you write OrderStatus.Shipped, and suddenly the code reads like a sentence. In this guide we will unpack what enums are, look at the two flavours TypeScript gives you (numeric and string), talk honestly about when they help, and compare them to the popular alternative: a union of string literals. By the end you will know not just how to write an enum, but when to reach for one and when to leave it on the shelf.

What an enum actually is

At its simplest, an enum is a way to give friendly names to a fixed group of values. Think of the four directions on a compass. There are exactly four of them, they never change, and each one deserves a clear name. That is a perfect fit for an enum.

enum Direction {
  Up,
  Down,
  Left,
  Right,
}

const move: Direction = Direction.Up;

Here Direction is the enum, and Up, Down, Left, and Right are its members. Each member is a named constant. When you type Direction. in your editor, autocomplete shows you the full list, so you cannot accidentally pass a value that does not belong.

The key idea is that the set is closed. A direction is one of those four things and nothing else. Enums shine exactly when a value is “one of a small, fixed list of options.”

Why not just use plain strings or numbers?

Plain values work, but they give you no safety net. If you type "shippd" by mistake, TypeScript happily accepts it as a string. An enum forces every value to come from the approved list, so typos turn into compile errors instead of runtime surprises.

Numeric enums

By default, a TypeScript enum is numeric. If you do not assign values yourself, members get numbers automatically, starting at 0 and counting up.

enum Direction {
  Up,    // 0
  Down,  // 1
  Left,  // 2
  Right, // 3
}

console.log(Direction.Left); // 2

You can also set the starting number, and the rest will continue from there:

enum StatusCode {
  Ok = 200,
  Created = 201,
  // the next ones still need explicit values here,
  // because 202, 203... are meaningful HTTP codes
  Accepted = 202,
}

Numeric enums have one quirk worth knowing: they are reverse-mapped. TypeScript lets you go from the number back to the name.

enum Direction { Up, Down, Left, Right }

console.log(Direction[0]); // "Up"
console.log(Direction.Up); // 0

That can be handy, but it also means a numeric enum compiles into a slightly larger object at runtime. More importantly, the underlying values are just numbers, which brings us to a real gotcha.

Numeric enums accept any number

With a plain numeric enum, TypeScript will let you assign any number to the enum type, even one that is not a member. Writing let d: Direction = 99 does not error by default. The number 99 is not Up, Down, Left, or Right, yet it slips through. This is a common source of confusion and a big reason many teams prefer string enums.

String enums

A string enum is one where you give every member an explicit string value. There is no auto-numbering, so you have to write each value out, but you get something very valuable in return: the runtime value is readable.

enum OrderStatus {
  Pending = "PENDING",
  Paid = "PAID",
  Shipped = "SHIPPED",
  Cancelled = "CANCELLED",
}

const status: OrderStatus = OrderStatus.Shipped;
console.log(status); // "SHIPPED"

Compare this to a numeric enum. If you log a numeric enum, you see 2, which means nothing to a human reading a server log or a debugger. Log a string enum and you see "SHIPPED". When something breaks at 2 a.m., that difference matters.

String enums also do not have the reverse mapping, and they do not silently accept arbitrary values the way numeric ones do. For most application code, string enums are the safer, clearer default.

Here is a small but realistic example. Say you are modelling a user account at ACY Partner Indonesia:

enum UserRole {
  Admin = "ADMIN",
  Editor = "EDITOR",
  Viewer = "VIEWER",
}

interface User {
  name: string;
  email: string;
  role: UserRole;
}

const jane: User = {
  name: "Jane Doe",
  email: "jane@example.com",
  role: UserRole.Editor,
};

function canPublish(user: User): boolean {
  return user.role === UserRole.Admin || user.role === UserRole.Editor;
}

Notice how canPublish reads almost like plain English. Nobody has to remember that “editor” is role number 1. The names carry the meaning.

When enums actually help readability

Enums are not magic, and they are not free. They earn their keep in specific situations:

  • The set is fixed and meaningful. Order statuses, user roles, days of the week, traffic-light colours. These are stable lists where each item deserves a name.
  • The value appears in many places. If UserRole.Admin shows up across a dozen files, a single enum definition keeps everyone honest and gives you one place to change.
  • You want autocomplete and grouping. Typing UserRole. and seeing the whole list is a genuine productivity win, and it documents the options for the next developer.

Here is a quick before-and-after to make the readability point concrete.

// Before: magic numbers, zero meaning
function describe(status: number): string {
  if (status === 0) return "Waiting";
  if (status === 1) return "Active";
  if (status === 2) return "Closed";
  return "Unknown";
}

// After: the intent is right there in the names
enum AccountStatus {
  Waiting = "WAITING",
  Active = "ACTIVE",
  Closed = "CLOSED",
}

function describe(status: AccountStatus): string {
  switch (status) {
    case AccountStatus.Waiting: return "Waiting";
    case AccountStatus.Active: return "Active";
    case AccountStatus.Closed: return "Closed";
  }
}

The second version is harder to misuse and easier to read months later. That is the whole pitch for enums.

The common alternative: a union of string literals

Here is something that surprises many newcomers: you often do not need an enum at all. TypeScript can express “one of a fixed set of strings” using a plain union of string literals.

type OrderStatus = "PENDING" | "PAID" | "SHIPPED" | "CANCELLED";

const status: OrderStatus = "SHIPPED"; // fine
const broken: OrderStatus = "shipped"; // error: not in the set

This does almost everything a string enum does. You get autocomplete, you get compile-time errors for invalid values, and the runtime value is just the readable string. The big difference is that a union literal type disappears at runtime — it is pure type information, so it adds zero code to your compiled JavaScript. An enum, by contrast, generates a real object that exists when your program runs.

So how do you choose? Here is an honest comparison.

Aspect String enum Union of string literals
Compile-time safety Yes Yes
Autocomplete Yes Yes
Readable runtime value Yes Yes (it is just the string)
Adds runtime code Yes (an object) No (erased)
Loop over all values Easy (Object.values) Need a manual array
Namespaced access (X.Member) Yes No (you use the raw string)
Plays well with plain JS data Needs mapping Strings match directly

A union of literals tends to win when you are dealing with data that arrives as plain strings anyway — for example a JSON response from an API. Your status field is already "SHIPPED", so a union type fits it like a glove with no conversion step. An enum can feel like extra ceremony there.

Enums tend to win when you want a single named home for the values, easy iteration over every member, and the ability to write UserRole.Admin instead of repeating the raw string everywhere.

A practical default

If you are unsure, start with a union of string literals. It is lighter, it maps cleanly to JSON data, and you can always promote it to an enum later if you find yourself needing iteration or a shared namespace. Many TypeScript codebases get along just fine without a single enum.

A word on const enum and other footguns

You may run into const enum, which tells TypeScript to inline the values and skip generating the runtime object. It sounds great for performance, but it interacts badly with certain build setups and isolated-module compilers, and it can cause confusing errors when code is split across files. For most projects, the simpler and more portable choice is either a regular string enum or, more often, a union of literals. Stay conceptual here: if you do not specifically need inlining, you probably do not want const enum.

The broader lesson is that enums are a tool, not a goal. Reach for them when a fixed, named set genuinely makes your code clearer, and feel completely free to skip them when a union of literals does the job with less weight.

Recap

Enums give names to a fixed set of related constants so your code stops relying on mystery numbers and loose strings. Here is what to carry away:

  • An enum is a named, closed set of values, like Direction.Up or OrderStatus.Shipped.
  • Numeric enums are the default; members auto-number from 0. They support reverse mapping but will accept arbitrary numbers, which is a safety hole.
  • String enums give each member an explicit string. They are readable in logs, safer, and the better everyday default among the two.
  • Enums help readability when the set is fixed, meaningful, used in many places, and you want autocomplete plus a single source of truth.
  • A union of string literals is the common, lightweight alternative. It gives the same compile-time safety, erases at runtime, and maps perfectly onto plain JSON strings — but loses easy iteration and namespaced access.

There is no single right answer. Pick the tool that makes the next person reading your code nod and understand it instantly. Often that is a string enum; surprisingly often, it is just a union of literals.

Tags:typescriptenumsconstantsfrontendtypesjavascript
728 × 90Ad Space AvailablePlace your ad here

Related Articles

See All Articles

You Might Also Like

Frontend best practices overview cover
Frontend / Fundamentals

Frontend Best Practices: An Overview

A friendly map of what good frontend really means — semantic HTML, clean CSS, unobtrusive JavaScript, responsive layouts, performance, and progressive enhancement, with pointers to go deeper.

Sep 20, 20268 min read
A Frontend Developer Roadmap cover with the path HTML to CSS to JavaScript
Frontend / Fundamentals

A Frontend Developer Roadmap: What to Learn, and in What Order

A calm, step-by-step learning path for beginners: HTML, CSS, JavaScript, developer tools, responsive and accessible design, a framework, TypeScript, and deployment, with the reasoning behind each step.

Sep 20, 202611 min read
Title card reading Styling and Interactivity over a dark blue ACY Partner background
Frontend / Fundamentals

How Styling and Interactivity Work

A beginner-friendly look at how CSS turns plain HTML into a designed layout, and how JavaScript adds behavior — the structure, style, behavior mental model for building web pages.

Sep 20, 20269 min read
Three front-end layers combining on one web page
Frontend / Fundamentals

How HTML, CSS, and JavaScript Work Together

A hands-on walkthrough for beginners: build one small button, give it structure with HTML, looks with CSS, and behavior with JavaScript, and watch the three layers cooperate on a real page.

Sep 20, 20268 min read
The Frontend Developer Toolkit cover with editor, browser, and CLI motifs
Frontend / Fundamentals

The Frontend Developer Toolkit

A friendly tour of the everyday tools a frontend developer relies on — code editor, browser and DevTools, terminal, version control, package managers, and a dev server — and what each one is actually for.

Sep 20, 202610 min read
Cover illustration for What Frontend Developers Do
Frontend / Fundamentals

What Frontend Developers Do: A Beginner's Guide to the Role

A clear, beginner-friendly look at what frontend developers actually do every day: turning designs into working interfaces, building reusable UI, and caring about responsiveness, accessibility, and speed.

Sep 20, 202610 min read