JS Guide
HomeQuestionsTopicsCompaniesResources
BookmarksSearch

Built for developers preparing for JavaScript, React & TypeScript interviews.

ResourcesQuestionsSupport
HomeQuestionsSearchProgress
HomeTopicstypescriptEnums vs Union Types
PrevNext
typescript
intermediate
8 min read

Enums vs Union Types

as-const
const-enum
enums
runtime
string-literals
unions

TypeScript enums compile to runtime objects (numeric enums auto-increment, string enums require explicit values), while union types are erased at compile time with zero overhead — most teams prefer string unions for string constants and reserve enums for cases needing runtime object access.

Key Points

1Runtime vs Compile-Time

Enums compile to JavaScript objects that exist at runtime. Union types are erased during compilation with zero bundle size impact.

2Numeric Enums

Auto-increment from 0 with bidirectional mapping (forward and reverse). The reverse mapping can cause unexpected iteration behavior.

3String Unions Preferred

Most teams use string literal unions for string constants — same DX (autocomplete, type safety) with no runtime cost and simpler code.

4as const Pattern

const STATUSES = ['active', 'inactive'] as const with typeof gives both a runtime array for iteration and a compile-time union type.

What You'll Learn

  • Compare numeric enums, string enums, and const enums
  • Explain why string literal unions are preferred over enums in most cases
  • Use the as const pattern to get both runtime values and compile-time types
  • Know when enums are still the right choice (reverse mapping, runtime object access)

Deep Dive

Enums define a set of named constants. TypeScript has three enum variants, each with different compilation behavior and tradeoffs.

Numeric Enums

Numeric enums auto-increment from 0 (or from an explicit starting value):

TypeScript
enum Direction { Up, Down, Left, Right }
// Up = 0, Down = 1, Left = 2, Right = 3

Numeric enums compile to a JavaScript object with both forward mapping (Direction.Up === 0) and reverse mapping (Direction[0] === 'Up'). This bidirectional mapping is useful when you need to display enum names for numeric values (e.g., logging, debugging).

The reverse mapping is a quirk that many developers don't expect: Object.keys(Direction) returns ['0', '1', '2', '3', 'Up', 'Down', 'Left', 'Right'] — both the number keys and string keys. This can cause bugs when iterating.

String Enums

String enums require explicit values for every member:

TypeScript
enum Status { Active = 'ACTIVE', Inactive = 'INACTIVE', Pending = 'PENDING' }

String enums compile to a forward-only mapping object — no reverse mapping. The values are readable in debugging output and JSON payloads. They're more predictable than numeric enums.

const Enums

const enum declarations are inlined at compile time — the enum object is never emitted:

TypeScript
const enum Color { Red, Green, Blue }
let c = Color.Red; // compiles to: let c = 0;

This eliminates the runtime object entirely, reducing bundle size. However, const enum has limitations: no reverse mapping, incompatible with isolatedModules (used by Babel, esbuild, SWC), and problematic across package boundaries. Many projects avoid const enum because of these issues — the TypeScript team has recommended against them in libraries.

Union Types as Alternative

String literal unions provide the same developer experience with zero runtime overhead:

TypeScript
type Status = 'active' | 'inactive' | 'pending';

Unions are erased at compile time — no runtime object, no bundle size cost. They provide autocomplete, type safety, and exhaustive switch checking (with never in the default case). The tradeoff: no runtime access to the list of valid values, and no built-in iteration.

To get the "list of values" that enums provide, use as const arrays:

TypeScript
const STATUSES = ['active', 'inactive', 'pending'] as const;
type Status = (typeof STATUSES)[number]; // 'active' | 'inactive' | 'pending'

This gives you both a runtime array for iteration/validation and a compile-time union type.

When to Use Which

  • String unions (most common): Use for string constants where you don't need a runtime object. Zero overhead, simpler, better TypeScript experience.
  • String enums: Use when you need a runtime namespace object for the values, or when interfacing with code that expects enum-like objects.
  • Numeric enums: Use when you need reverse mapping (number → name) or when interfacing with APIs that use numeric codes.
  • as const arrays: Use when you need both a runtime array of values and a compile-time union type.

Key Interview Distinction

Enums exist at runtime as JavaScript objects — they add code to your bundle. Union types are erased at compile time with zero overhead. String unions are preferred for most use cases. Use as const arrays when you need both runtime iteration and type safety. Avoid const enum in libraries and projects using transpilers like esbuild or Babel.

Fun Fact

Enums are one of the few TypeScript features that are NOT just type-level — they generate runtime JavaScript code. The TypeScript team has said that if they were designing TypeScript today, they probably wouldn't add enums at all. The `as const` + union pattern didn't exist when enums were designed, and it covers most use cases with less complexity.

Learn These First

Basic Types

beginner

Union & Intersection Types

beginner

Continue Learning

TypeScript Fundamentals

beginner

Interfaces vs Type Aliases

beginner

Practice What You Learned

What are enums in TypeScript and when should you use them vs union types?
mid
enums
Enums define named constants that can be numeric or string values. They exist at runtime as objects. Union types ('a' | 'b') are often preferred for string literals as they're lighter weight and better tree-shaken. Use enums when you need runtime access to values.
Previous
Declaration Files
Next
Function Types
PrevNext