Enums

Reviewed & published by Brayan K

An enum is a named set of related constants — like Direction.North or LogLevel.Error — that gives meaningful names to a fixed group of numeric or string values.

Part of the free TypeScript course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

💡 Real-World Analogy

An enum is like the buttons on a vending machine. Each button has a friendly label (Cola, Water, Juice) but maps to an internal slot number the machine actually uses. You press the name; the machine works with the number. A numeric enum even keeps a little lookup card going the other way, so it can tell you which label slot 2 belongs to.

1. Numeric Enums & Reverse Mapping

A numeric enum assigns each member a number. By default they auto-increment from 0 (North = 0, East = 1, …), though you can set values explicitly (OK = 200). The interesting twist is the reverse mapping: at runtime the generated object maps both name → number and number → name, so Direction[0] gives back "North".

// Numeric enum — members auto-number from 0 unless you set values:
enum Direction { North, East, South, West } // 0, 1, 2, 3

let d: Direction = Direction.East;  // d is 1
console.log(Direction[1]);          // "East"  (reverse mapping)

// You can start or set the numbers explicitly:
enum HttpStatus { OK = 200, NotFound = 404, ServerError = 500 }
let code: HttpStatus = HttpStatus.NotFound; // 404
// An ENUM is a named set of related constants.
// In TypeScript:
//   enum Direction { North, East, South, West }   // auto-numbered 0,1,2,3
// At RUNTIME, TypeScript compiles that into a plain object. Here is the
// EXACT object a numeric enum becomes — including its "reverse mapping":

const Direction = {
  North: 0, East: 1, South: 2, West: 3,  // name -> number
  0: "North", 1: "East", 2: "South", 3: "West", // number -> name (reverse map!)
};

console.log(Direction.North);      // 0
console.log(Direction.West);       // 3
console.log(Direction[0]);         // North  (reverse mapping)
console.log(Direction[3]);         // West   (reverse mapping)

// So you can go BOTH ways: name to number, and number back to name.
const dir = Direction.East;        // 1
console.log("dir is", dir, "=", Direction[dir]); // dir is 1 = East

// In a .ts file you'd just write:
//   enum Direction { North, East, South, West }
//   Direction.North // 0
//   Direction[0]    // "North"

2. String Enums

A string enum gives every member an explicit string value: enum LogLevel { Debug = "DEBUG", Info = "INFO" }. The big win is readability — when you log or serialise a string enum member you see "DEBUG", not a mysterious 0. The trade-off: string enums have no reverse mapping, so you can only go name → value.

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

function log(level: LogLevel, message: string): string {
  return "[" + level + "] " + message;
}
log(LogLevel.Error, "disk full"); // [ERROR] disk full

// No reverse mapping for string enums:
// LogLevel["INFO"]  // undefined
// STRING enums give each member an explicit string value.
// In TypeScript:
//   enum LogLevel { Debug = "DEBUG", Info = "INFO", Error = "ERROR" }
// They are easier to read in logs and have NO reverse mapping.
// Here is the plain object a string enum compiles to:

const LogLevel = {
  Debug: "DEBUG",
  Info: "INFO",
  Error: "ERROR",
};

console.log(LogLevel.Info);   // INFO
console.log(LogLevel.Error);  // ERROR

function log(level, message) {
  return "[" + level + "] " + message;
}
console.log(log(LogLevel.Debug, "starting up")); // [DEBUG] starting up
console.log(log(LogLevel.Error, "disk full"));   // [ERROR] disk full

// Note: a string enum has NO reverse mapping, so LogLevel["INFO"]
// is undefined — only name -> value works.
console.log(LogLevel["INFO"]); // undefined

// In a .ts file:
//   enum LogLevel { Debug = "DEBUG", Info = "INFO", Error = "ERROR" }

3. Enum vs Union of Literals (and const enums)

Both an enum and a union of string literals describe a fixed set of allowed values. The difference is cost: a regular enum emits a runtime object, while a union of literals ("admin" | "editor" | "viewer") adds zero runtime code. A const enum splits the difference — it's inlined at compile time so each use is replaced by its literal value and no object is generated.

// Three ways to model the same fixed set:

// (a) Regular enum — generates a runtime object
enum Role { Admin = "admin", Editor = "editor", Viewer = "viewer" }

// (b) const enum — inlined, NO runtime object emitted
const enum Size { Small, Medium, Large }
let s = Size.Medium; // compiles to:  let s = 1;

// (c) Union of literals — zero runtime code, very common today
type RoleU = "admin" | "editor" | "viewer";
// ENUM vs UNION-OF-LITERALS — two ways to express a fixed set.
// A union of literals needs NO runtime object at all:
//   type Role = "admin" | "editor" | "viewer";
// An enum DOES create a runtime object (extra generated code).

// Modelling the same idea both ways at runtime:

// 1) Union-of-literals style — just use the strings directly:
const ROLES = ["admin", "editor", "viewer"]; // mirrors: "admin" | "editor" | "viewer"
function canPublish(role) {
  return role === "admin" || role === "editor";
}
console.log(canPublish("admin"));  // true
console.log(canPublish("viewer")); // false

// 2) Enum style — a named object holding the values:
const Role = { Admin: "admin", Editor: "editor", Viewer: "viewer" };
console.log(Role.Admin);                 // admin
console.log(canPublish(Role.Editor));    // true

// Both describe the SAME three allowed values; the union version
// adds zero runtime code, which is why many teams prefer it.

🎯 Your Turn

Mirror a string enum as a plain object. Fill in the two blanks marked ___, then run it.

// 🎯 YOUR TURN — replace each ___ then press "Try it Yourself".
// We're mirroring a STRING enum as a plain object:
//   enum Planet { Mercury = "MERCURY", Venus = "VENUS", Earth = "EARTH" }

const Planet = {
  Mercury: ___,   // 👉 the string "MERCURY"
  Venus: "VENUS",
  Earth: "EARTH",
};

console.log(Planet.Mercury); // MERCURY
console.log(Planet.Earth);   // EARTH

// Use it in a message:
console.log("Welcome to " + Planet.___); // 👉 Venus  ->  Welcome to VENUS

// ✅ Expected output:
//    MERCURY
//    EARTH
//    Welcome to VENUS

Common Errors (and the fix)

Pro Tips

Mini-Challenge: Hand-build a Numeric Enum

No blanks this time — just a brief and a starting outline. Recreate the object a numeric enum compiles to (including reverse mapping), run it, and check your output against the example in the comments.

// 🎯 MINI-CHALLENGE: model a numeric enum WITH reverse mapping by hand
// TypeScript's  enum Suit { Hearts, Diamonds, Clubs, Spades }  compiles to an
// object that maps BOTH name->number AND number->name. Build that object:
//
// 1. Create  Suit  with Hearts:0, Diamonds:1, Clubs:2, Spades:3
//    AND the reverse entries 0:"Hearts", 1:"Diamonds", 2:"Clubs", 3:"Spades".
// 2. Log  Suit.Clubs           (expect 2)
// 3. Log  Suit[3]              (expect Spades)
// 4. Given  const card = Suit.Diamonds;  log  "card = " + Suit[card]
//    (expect: card = Diamonds)
//
// ✅ Expected output:
//    2
//    Spades
//    card = Diamonds

// your code here

🎉 Lesson Complete

Practice quiz

By default, what value does the first member of 'enum Direction { North, East, South, West }' get?

  • 1
  • North
  • 0
  • undefined

Answer: 0. Numeric enums auto-number from 0, so North is 0, East is 1, and so on.

What does 'Direction[0]' return for that numeric enum at runtime?

  • "North"
  • 0
  • undefined
  • an error

Answer: "North". Numeric enums have a reverse mapping, so indexing by the number returns the member name string.

Why does a numeric enum's runtime object store each member twice?

  • For performance
  • To support const enums
  • It is a TypeScript bug
  • Because of the reverse mapping (name to number AND number to name)

Answer: Because of the reverse mapping (name to number AND number to name). The reverse mapping means the object holds both name to number and number to name entries.

What is 'LogLevel["INFO"]' for 'enum LogLevel { Debug = "DEBUG", Info = "INFO" }'?

  • "Info"
  • undefined
  • "INFO"
  • 1

Answer: undefined. String enums have NO reverse mapping, so looking up by the value returns undefined.

What is the main readability win of a string enum over a numeric one?

  • Logged or serialised members show a readable value like "DEBUG"
  • It is faster at runtime
  • It has a reverse mapping
  • It uses less memory

Answer: Logged or serialised members show a readable value like "DEBUG". String enum members print their explicit string value, far easier to debug than a bare number.

What does a 'const enum' emit at runtime?

  • A frozen object
  • A plain object with reverse mapping
  • No runtime object at all (values are inlined)
  • A string-keyed Map

Answer: No runtime object at all (values are inlined). A const enum is inlined at compile time; each use is replaced by its literal value and no object is generated.

Which alternative to an enum adds ZERO runtime code?

  • A const enum
  • A union of string literals
  • A numeric enum
  • A namespace

Answer: A union of string literals. A union of literals like "admin" | "editor" is purely type-level and emits no runtime object.

How do you give enum members explicit numeric values?

  • enum HttpStatus { OK: 200 }
  • enum HttpStatus { 200, 404 }
  • You cannot set them
  • enum HttpStatus { OK = 200, NotFound = 404 }

Answer: enum HttpStatus { OK = 200, NotFound = 404 }. Assign with '=' as in 'enum HttpStatus { OK = 200, NotFound = 404 }'.

What happens to later members if you insert a member in the middle of a numeric enum?

  • Nothing changes
  • Every later member's auto-number shifts up by one
  • The enum throws
  • Only the new member gets a number

Answer: Every later member's auto-number shifts up by one. Auto-numbering shifts all following members, which can break persisted numeric values.

A string enum member compared with a raw string like 'level === "ERROR"' may complain because:

  • Strings cannot be compared
  • Enums are runtime-only
  • Each string enum member is its own type; compare against LogLevel.Error instead
  • It needs a reverse mapping

Answer: Each string enum member is its own type; compare against LogLevel.Error instead. A string enum member has its own literal type, so compare against the enum member (LogLevel.Error).

Continue this course

Frequently asked questions

What does an enum actually become at runtime?

A numeric or string enum compiles to a plain JavaScript object. A numeric enum also gets a 'reverse mapping' so the object holds both name→number and number→name entries (e.g. Direction.North is 0 and Direction[0] is "North"). String enums compile to a simple name→string object with no reverse mapping.

What is a const enum and when should I use it?

A const enum (const enum Color { ... }) is fully inlined at compile time — the compiler replaces each Color.Red with its literal value and emits NO runtime object at all, so it's smaller and faster. Use it for purely internal constant sets where you don't need to iterate the enum object or look up members dynamically.

Should I use an enum or a union of string literals?

Many modern codebases prefer a union of string literals (type Role = "admin" | "editor") because it adds zero runtime code, prints readable values, and works seamlessly with JSON. Choose an enum when you specifically want a named runtime object, numeric auto-incrementing values, or reverse mapping from value back to name.

Why does Direction[0] give me a string back?

That's the reverse mapping of a numeric enum. Because numeric enums store both directions in the same object, indexing with a number returns the member name. String enums do NOT do this — there's no reverse mapping — so LogLevel["INFO"] is undefined.