enum
Declares a named set of constants that emits a real runtime object of keys and values.
enum Name {
Member, // numeric, auto-incremented from 0
Initialized = 1, // numeric, subsequent members continue from here
StringMember = "TEXT", // string, no auto-increment
Computed = "abc".length // computed member
}- Added in TypeScript 1.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$Name | identifier | Creates both a type and a value. The value is a real object present in the emitted JavaScript. |
$Member | identifier | A member name. Without an initializer it takes 0 for the first member, or the previous member's value plus one. |
$initializeroptional | number | string | constant enum expression= previous member + 1, or 0 for the first member | Optional value. Constant expressions may use literals, earlier members, parentheses, the unary operators + - ~ and the binary operators + - * / % << >> >>> & | ^. Anything else makes the member computed. |
Errors
Errors at compile time if a member without an initializer follows a string or computed member, since there is no value to increment from.
Changelog
- PHP 2.4String enums were added. String members never get a reverse mapping, so `Colors["RED"]` does not give you back the member name the way a numeric enum would.
- PHP 5.0All enums are now treated as union enums where possible, so enum member types narrow more aggressively and comparisons between unrelated members are reported as having no overlap.
- PHP 5.8The erasableSyntaxOnly flag rejects enum declarations outright, because they emit runtime code and cannot be stripped. Required if you run TypeScript directly under Node's type-stripping mode.
Unlike almost everything else on this page, enum is not erased. It emits a real object, which is why an enum can be passed to a function, iterated, or handed to code that has never heard of TypeScript.
Numeric versus string
A numeric enum gets a reverse mapping: the emitted object stores Obj[Obj["Up"] = 1] = "Up", so both Direction.Up and Direction[1] work. A string enum stores only the forward direction. That asymmetry is the single biggest source of surprise.
When to reach for it
String enums are the safe default: their runtime values are self-describing in logs, over the wire and in a database column, and they survive reordering. Numeric enums earn their keep for bit flags (Read = 1 << 1) and when you genuinely need value-to-name lookup.
What trips people up
A numeric enum's keys are not just the member names — the reverse mapping means Object.keys() returns the numbers too, so naive iteration yields each member twice. Numeric enums are also loose about assignment in older configurations, and inserting a member in the middle silently renumbers everything after it, corrupting any value already persisted. If your build only strips types, reach for an as const object instead.
Example
When to use it
- Replace magic string literals for an order status with named members that log readably.
- Model permission bit flags that can be combined and tested with bitwise operators.
- Map a numeric protocol code back to a human-readable name using the reverse mapping.
- Give a function a parameter that only accepts a fixed set of documented constants.
Frequently asked questions
Should I use an enum or an as const object in TypeScript?
Prefer an `as const` object unless you specifically need the enum's reverse mapping or nominal-ish member types. The handbook itself presents `as const` as the modern alternative because it stays plain JavaScript, works under type-stripping build tools, and yields a union type via `typeof Obj[keyof typeof Obj]`.
Why does Object.keys on an enum return numbers as well as names?
Because numeric enums emit a reverse mapping, so the runtime object contains both name-to-value and value-to-name entries. Filter with `Object.keys(E).filter(k => isNaN(Number(k)))` to get just the member names, or use a string enum, which has no reverse mapping.
Why can't I look up a string enum member by its value?
String enums deliberately have no reverse mapping — only the forward name-to-value entries are emitted, so `LogLevel["ERROR"]` is undefined. If you need value-to-name lookup, build the inverse object yourself or use a numeric enum.
More examples
Iterating a numeric enum without doubling every member
The first log shows the double-counting that catches everyone: a numeric enum object holds six entries for three members, because the reverse mapping adds "0" -> "Pending" alongside "Pending" -> 0. Filtering out keys that parse as numbers recovers just the member names. A string enum needs no filter, since it has no reverse mapping.
enum Status {
Pending,
Active,
Closed,
}
// Wrong: the reverse mapping puts the numbers in here too.
console.log(Object.keys(Status));
// [ '0', '1', '2', 'Pending', 'Active', 'Closed' ]
// Right: keep only the entries whose value is a number.
const names = Object.keys(Status).filter((k) => isNaN(Number(k)));
console.log(names);
// [ 'Pending', 'Active', 'Closed' ]
const values = names.map((n) => Status[n as keyof typeof Status]);
console.log(values);
// [ 0, 1, 2 ]Bit flags with a constant enum expression
Shift and bitwise-or expressions over earlier members are constant enum expressions, so ReadWrite is folded to 6 at compile time rather than evaluated at runtime. The output confirms the combined flag decomposes back into its parts. This is the case where numeric enums beat string enums outright — you cannot bitmask strings.
enum FileAccess {
None = 0,
Read = 1 << 1, // 2
Write = 1 << 2, // 4
ReadWrite = Read | Write, // 6
}
function describe(access: FileAccess): string {
if (access === FileAccess.None) return "none";
const parts: string[] = [];
if (access & FileAccess.Read) parts.push("read");
if (access & FileAccess.Write) parts.push("write");
return parts.join("+");
}
console.log(FileAccess.ReadWrite, describe(FileAccess.ReadWrite));
console.log(describe(FileAccess.Read), describe(FileAccess.None));
// 6 read+write
// read none
Discussion