Q9 of 38 · TypeScript
When should you use a TypeScript enum versus a string literal union type?
TypeScriptJuniortypescriptenumsunion-typesas-constfundamentals
Short answer
Short answer: String literal union types (`'pending' | 'active' | 'done'`) are preferred for most cases — they are lightweight, tree-shakeable, and produce readable JavaScript. Enums generate runtime objects and are useful when you need a reverse mapping, a guaranteed set of named numeric codes, or a shared constant object across modules.
Detail
Both mechanisms model a fixed set of values, but they differ in runtime behaviour and idiomatic use.
String literal unions:
- No runtime output — they are erased at compile time
- Readable: values in error messages and logs are plain strings
- Tree-shakeable: no bundle impact
- Can be used in type guards easily:
if (status === "pending") - Easy to extend or intersect with other union types
const enum:
- Inlined at compile time — no runtime object, just the literal values
- Slightly more performant (no object property lookup)
- Cannot be used across module boundaries with
isolatedModules(affects Babel, Vite, esbuild)
Regular enum:
- Generates a runtime JavaScript object (
{ Pending: "pending", pending: "Pending" }for string enums — or number reverse mapping for numeric enums) - Useful when: you need the reverse mapping, you want named constants with guaranteed values shared at runtime, or you integrate with code that expects an enum object
- Numeric enums have surprising behaviour: unspecified values are auto-incremented integers — if the order changes, values shift silently
Recommendation: Prefer string literal unions in TypeScript codebases. Use enums when the runtime object is explicitly needed or when aligning with a legacy API.
// EXAMPLE
// String literal union — preferred
type Status = "pending" | "active" | "done";
const s: Status = "active";
// s = "unknown"; // Error
// Enum — generates runtime object
enum Direction {
Up = "UP",
Down = "DOWN",
}
const d = Direction.Up; // "UP"
console.log(Direction); // { Up: "UP", Down: "DOWN" } — runtime object
// const enum — inlined, no runtime object
const enum Color { Red = "RED", Green = "GREEN" }
const c = Color.Red; // compiled to: const c = "RED"
// Type guard works with both
function isActive(s: Status): s is "active" {
return s === "active";
}
// Union type from array (common pattern in QA tools)
const LEVELS = ["junior", "mid", "senior", "lead"] as const;
type Level = typeof LEVELS[number]; // "junior" | "mid" | "senior" | "lead"// WHAT INTERVIEWERS LOOK FOR
Runtime vs compile-time distinction. String union as the default choice. When enums are appropriate. The `as const` + `typeof arr[number]` pattern for deriving unions from arrays. Awareness of const enum's `isolatedModules` limitation.
// COMMON PITFALL
Using numeric enums without explicit values — if the order changes, all downstream values shift silently. Always use string enums or string unions to keep values meaningful and stable.