# Enums

## 1. What an Enum Is

An `enum` defines a named set of constant values. Unlike most TypeScript constructs, an enum is one of the rare features that is **not** fully erased — it produces real runtime code (full detail in "Under the Hood").

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

let move: Direction = Direction.Up;
console.log(move); // 0
```

## 2. Numeric Enums — Auto-Incrementing

```ts
enum Status {
  Pending,   // 0
  Active,     // 1
  Closed,     // 2
}

enum HttpCode {
  OK = 200,
  NotFound = 404,
  ServerError = 500,
}

enum Mixed {
  A,        // 0
  B = 10,    // explicitly 10
  C,         // 11 — continues incrementing from the previous value
}
```

By default, numeric enum members start at `0` and increment by `1`; you can override any member's value explicitly, and subsequent unset members continue incrementing from there.

## 3. String Enums

```ts
enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT",
}

console.log(Direction.Up); // "UP" — readable at runtime (e.g. in logs, network payloads)
```

String enums require **every** member to have an explicit value (no auto-increment) and don't support reverse mapping (section 4) — but they produce far more debuggable runtime values than numeric enums' plain integers.

## 4. Reverse Mapping — Numeric Enums Only

```ts
enum Status { Pending, Active, Closed }

console.log(Status.Active);   // 1        — forward mapping: name → value
console.log(Status[1]);        // "Active" — reverse mapping: value → name
```

Numeric enums get an automatic reverse lookup; string enums do not (a string enum member's key literally collides with nothing reversible the way sequential integers do).

## 5. `const enum` — Fully Inlined, No Runtime Object

```ts
const enum Direction {
  Up,
  Down,
}

let move = Direction.Up; // compiles to: let move = 0;  — NO Direction object exists at runtime at all
```

A `const enum` is a compiler-only construct: every reference to a member is replaced with its literal value directly at compile time, and no runtime object is generated whatsoever — the fastest and smallest option, at the cost of losing reverse mapping and runtime introspection entirely.

## 6. Enums vs Union of Literals (Preview)

```ts
enum StatusEnum { Pending, Active, Closed }
type StatusUnion = "pending" | "active" | "closed";
```

Both express a fixed set of allowed values. A union of string literals has zero runtime footprint (fully erased, per the Union Types topic) and is often preferred in modern TypeScript codebases specifically to avoid enums' non-standard runtime behavior — full comparison belongs in the Union Types topic, but it's worth knowing this tradeoff exists before reaching for `enum` by default.

## 7. Under the Hood: What an Enum Actually Compiles To

### A regular (non-const) enum compiles to an IIFE building a real object

```ts
enum Status { Pending, Active, Closed }
```

compiles to roughly:

```js
var Status;
(function (Status) {
  Status[(Status["Pending"] = 0)] = "Pending";
  Status[(Status["Active"] = 1)] = "Active";
  Status[(Status["Closed"] = 2)] = "Closed";
})(Status || (Status = {}));
```

![The enum compiles into an IIFE that builds a real object with both forward (name to value) and reverse (value to name) entries](diagrams/01-enum-compilation.svg)

Reading this carefully: each line does TWO assignments — `Status["Pending"] = 0` (the forward mapping) and then uses that same assignment's *result* (`0`) as the key for `Status[0] = "Pending"` (the reverse mapping) — this single dense line is exactly how numeric enums end up supporting both `Status.Pending` and `Status[0]` lookups. This is real, executed JavaScript — a genuine object gets built at module load time — which is exactly why `enum` is one of the few TypeScript features that isn't purely a compile-time-only construct.

### String enums skip the reverse-mapping half

```ts
enum Direction { Up = "UP" }
```

compiles to:

```js
var Direction;
(function (Direction) {
  Direction["Up"] = "UP";
})(Direction || (Direction = {}));
```

Only the forward assignment happens — there's no `Direction["UP"] = "Up"` line — which is the concrete compiled reason string enums have no reverse mapping (section 4): the compiler simply never emits the code that would create one.

### `const enum` never creates the object at all

For a `const enum`, the compiler doesn't emit any IIFE or object — every usage site is scanned and the member reference is replaced with its literal value directly, inline, at every call site. This is why `const enum` is sometimes disallowed in certain build setups (e.g. Babel-based toolchains, or `isolatedModules` mode): those tools compile files independently without full cross-file type information, and can't perform this inlining substitution correctly without seeing the enum's actual declaration at every use site.

## 8. Common Pitfalls

1. **Reordering numeric enum members** — auto-incremented values shift for everything after the change, silently breaking any previously-serialized/stored numeric values that assumed the old ordering.
2. **Expecting string enums to support reverse mapping** — they don't; only numeric enums get the value→name lookup, per section 4/7.
3. **Using `const enum` across module boundaries with incompatible build tooling** — inlining requires full type information at compile time; some transpilers/bundlers can't do this safely and will error or produce incorrect output.
4. **Reaching for `enum` reflexively instead of considering a literal union** — enums carry real runtime cost and non-standard behavior a literal union type doesn't; many modern style guides prefer literal unions for this reason (see section 6).

## 9. Quick Reference

```
enum X { A, B }              → real runtime object, forward + reverse mapping
enum X { A = "a" }             → string enum — forward mapping only, must set every value explicitly
const enum X { A, B }           → fully inlined, NO runtime object at all
```
