8.1 Numeric Enums: Auto-Incrementing Values and Reverse Mapping
Let’s talk about the first enums you’ll meet, the ones that look suspiciously like numbers. Because, well, they are numbers. A numeric enum is essentially a fancy way to give friendly names to a set of numeric values. TypeScript’s approach here is pragmatic, a bit clever, and comes with one of its weirder party tricks: reverse mapping.
The Basics and Auto-Incrementing Madness
You define a numeric enum using the enum keyword. Here’s the classic example:
enum Direction {
Up,
Down,
Left,
Right,
}
Seems simple, right? But what are the actual values of Direction.Up or Direction.Down? Let’s console.log them:
console.log(Direction.Up); // Output: 0
console.log(Direction.Down); // Output: 1
console.log(Direction.Left); // Output: 2
console.log(Direction.Right); // Output: 3
By default, numeric enums are auto-incrementing, starting from 0. This is fantastic for when you care more about having a unique identifier for each member than you do about the specific number itself. It saves you from the mental overhead of manually assigning values and avoids typos that would create duplicates.
You’re not shackled to starting at zero. You can initialize the first member with any number you like, and the auto-incrementing will carry on from there.
enum StatusCodes {
OK = 200,
Created, // 201
Accepted, // 202
BadRequest = 400,
Unauthorized, // 401
Forbidden, // 402 (Wait, that's not right... more on this pitfall later)
}
You can also assign any number to any member, breaking the increment chain entirely. This is useful for enums that represent non-sequential, well-known values (like HTTP status codes).
enum HttpStatus {
OK = 200,
NotFound = 404,
InternalServerError = 500,
// Next member would be 501 if we added one
}
The Voodoo: Reverse Mapping
Here’s where things get weird and most people go, “Wait, TypeScript does what?” For numeric enums, TypeScript doesn’t just create an object that maps names to values (Direction["Up"] => 0). It also, rather helpfully and sometimes confusingly, creates a reverse mapping from values back to names (Direction[0] => "Up").
Let’s look at the compiled JavaScript for our Direction enum:
// What TypeScript compiles our enum into
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Down"] = 1] = "Down";
Direction[Direction["Left"] = 2] = "Left";
Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));
This clever/insane IIFE is what generates the two-way mapping. The line Direction[Direction["Up"] = 0] = "Up"; is executed as:
Direction["Up"] = 0-> assigns the value0to the property"Up"- The expression now evaluates to
0, so it becomesDirection[0] = "Up"
This is why you can do this:
let myDirection: Direction = Direction.Up; // value is 0
console.log(Direction[myDirection]); // Output: "Up"
This is incredibly useful for debugging and logging. Instead of a cryptic number showing up in your logs, you can get the human-readable name. It feels like magic, and it is—the good kind.
Pitfalls and Questionable Choices
This power comes with responsibility and a few footguns.
The Discontinuous Increment Pitfall: Look back at my
StatusCodesexample. I setBadRequest = 400after a sequence that ended at202. The next member,Unauthorized, auto-increments to401, which is correct. But the member after that,Forbidden, becomes402. This is wrong. The actual HTTP status code for Forbidden is403. This is a easy mistake to make. The best practice here is to either always assign values explicitly for non-sequential enums or initialize every member after a break.// Better: Explicit everywhere enum CorrectStatusCodes { OK = 200, Created = 201, Accepted = 202, BadRequest = 400, Unauthorized = 401, Forbidden = 403, // Correct NotFound = 404, }The “It’s Just a Number” Problem: The type safety of enums exists almost entirely in the TypeScript world. At runtime, an enum value is just a number. This means nothing stops you from doing something like
let problem: Direction = 999;. This is completely valid TypeScript and will only fail, silently, at runtime when you try to doDirection[999]and getundefined. You have to trust that the values in your program are within the expected set.Strange Lookups: Because of the reverse mapping, your enum object has all its number keys as strings (since object keys in JavaScript are always strings or symbols). This means
Direction["0"]is also valid and returns"Up". It’s consistent with how JavaScript objects work, but it can look odd if you’re not expecting it.
In practice, numeric enums are workhorses. Use them when the numeric value itself has meaning (like a status code) or when you just need a set of distinct numbers and don’t want to manage them yourself. Just be wary of the increment logic and remember that mysterious reverse mapping is there, waiting to help you debug your way out of a problem.