Enums

10 questions found

A numeric enum assigns sequential integer values starting at 0 to each member by default, unless you explicitly assign a starting value — subsequent members then auto-increment from there.
enum Direction {
  Up,    // 0
  Down,  // 1
  Left,  // 2
  Right  // 3
}
console.log(Direction.Up); // 0
Real-world example Representing a fixed set of directions, statuses, or days of the week with meaningful names instead of magic numbers.

Common follow-ups: What happens if you assign a custom starting value to the first member, like Up = 1?

Primitive Types Arrays & Tuples
A string enum explicitly assigns a distinct string literal to each member (no auto-incrementing), which makes debugging easier since logged/serialized values are meaningful text rather than opaque numbers, and prevents accidental numeric type coercion between unrelated enums.
enum Status {
  Active = 'ACTIVE',
  Inactive = 'INACTIVE',
  Pending = 'PENDING'
}
console.log(Status.Active); // 'ACTIVE'
Real-world example Representing an order or subscription status where the readable string value is also what gets sent to or received from an API.

Common follow-ups: Can string enum members reference other members' values automatically, the way numeric enums can compute from a previous value?

Union & Intersection Types
Numeric enums generate a two-way lookup object at runtime — you can go from name to value AND from value back to name (Direction[0] === 'Up'). String enums only generate a one-way object, since having numeric values map back to keys would be ambiguous/meaningless for string values.
enum Direction { Up, Down }
console.log(Direction.Up);   // 0
console.log(Direction[0]);   // 'Up' -- reverse mapping, numeric enums only
Real-world example Converting a stored numeric status code back into its readable name for logging or display purposes.

Common follow-ups: Does this reverse mapping affect the size of the generated JavaScript output?

Primitive Types Arrays & Tuples
A const enum is fully inlined at every usage site during compilation and produces NO actual runtime object at all — usages of Direction.Up are replaced directly with the literal value 0 in the emitted JavaScript, resulting in smaller, faster output, at the cost of losing the ability to iterate over enum values at runtime.
const enum Direction { Up, Down }
let d = Direction.Up;
// compiles to roughly: let d = 0; -- no Direction object exists at runtime
Real-world example Using const enums for performance-sensitive, high-frequency code where the small object-creation overhead of a regular enum matters.

Common follow-ups: Why are const enums considered risky or discouraged in some bundler/build setups (like with Babel or isolatedModules)?

tsconfig & Compiler Options
Enum members can be constant (a literal, or computed from prior members) or 'computed' (derived from a non-constant expression) — TypeScript requires that any UNINITIALIZED member following a computed one must have an explicit value, since it can no longer safely auto-increment from an unknown runtime expression.
function getValue() { return 10; }
enum Mixed {
  A = 1,
  B = getValue(), // computed
  // C would need an explicit value here -- can't auto-increment after a computed member
  C = 20
}
Real-world example Rarely needed directly, but useful to understand when debugging why TypeScript demands an explicit value after certain enum members.

Common follow-ups: Why does TypeScript enforce this restriction specifically after computed members?

Primitive Types Arrays & Tuples
Union literal types (`type Status = 'active' | 'inactive'`) require zero runtime code (fully erased, unlike regular enums which generate a real object), work naturally with plain string values from JSON/APIs without any conversion, and integrate more smoothly with generic type inference — enums add real runtime overhead and some quirky edge-case behaviors that literal unions avoid entirely.
// Enum: generates a runtime object, less flexible with raw string values
enum Status { Active = 'ACTIVE', Inactive = 'INACTIVE' }

// Literal union: zero runtime cost, works directly with API string values
type Status = 'ACTIVE' | 'INACTIVE';
Real-world example Choosing a literal union over an enum specifically because API responses already send raw string values that need no conversion.

Common follow-ups: What ergonomic advantages does an actual enum still retain over a literal union, like autocomplete-friendly namespacing?

Union & Intersection Types
Unlike most structural comparisons, TypeScript treats numeric enum members as NOMINALLY distinct from each other by name even when their underlying values match — you cannot assign a member of one numeric enum to a variable typed as a different numeric enum, even if both equal 0, though plain numbers CAN still be assigned to a numeric enum variable.
enum A { X = 0 }
enum B { Y = 0 }
let a: A = A.X;
// let bad: A = B.Y; // Error: Type 'B' is not assignable to type 'A'
let ok: A = 0; // OK -- plain numbers are still assignable
Real-world example Understanding a confusing compiler error when trying to pass one enum's member where a structurally-identical-looking different enum is expected.

Common follow-ups: Does this same nominal-like distinctness apply to string enums as well?

Structural Typing & Duck Typing
Use Object.values(EnumName) to get an array of the enum's values (safe for string enums, since they don't generate reverse-mapping numeric keys), optionally typed explicitly as an array of the enum type for full type safety when iterating.
enum Status { Active = 'ACTIVE', Inactive = 'INACTIVE' }
const allStatuses = Object.values(Status) as Status[];
allStatuses.forEach(s => console.log(s)); // 'ACTIVE', 'INACTIVE'
Real-world example Populating a dropdown's options dynamically from every possible enum value, without hardcoding the list separately.

Common follow-ups: Why does the same Object.values() approach behave differently (and need filtering) for numeric enums?

Primitive Types Arrays & Tuples
declare enum describes an enum that ALREADY EXISTS at runtime (typically defined in plain JavaScript or another compiled module) without generating any new runtime code itself — used in .d.ts files to describe a third-party library's runtime enum-like object so TypeScript can type-check code that references it.
// library.d.ts
declare enum Color {
  Red,
  Green,
  Blue
}
// no JS object is generated here -- it must already exist at runtime, provided by the library
Real-world example Writing a declaration file for a JavaScript library that exports an enum-like object, so TypeScript users of that library get type checking.

Common follow-ups: How does this interact with the 'const enum' inlining behavior when the library itself defines a const enum?

Declaration Files
Assign each enum member a distinct power-of-two value so they can be safely combined with the bitwise OR operator into a single number representing multiple flags at once; check whether a specific flag is present using the bitwise AND operator against that flag's value.
enum Permission {
  Read = 1 << 0,   // 1
  Write = 1 << 1,  // 2
  Delete = 1 << 2  // 4
}
const userPerms = Permission.Read | Permission.Write; // 3
const canWrite = (userPerms & Permission.Write) !== 0; // true
Real-world example Representing a user's combined set of permissions (read/write/delete) compactly as a single stored integer.

Common follow-ups: What are the readability trade-offs of bitwise flag enums compared to using an array or Set of permission strings?

Never Unknown & Void Types