Skip to content
Enums and Literal Types
step 1/7

Reading — step 1 of 7

Learn

~3 min readType System Essentials

TypeScript has TWO ways to represent a closed set of values: enums (legacy) and literal-type unions (modern). The Handbook now nudges you toward literal unions for most use cases.

String literal unions

A type made of specific string values:

type Status = 'pending' | 'done' | 'failed';

function setStatus(s: Status): void {
    console.log(`status: ${s}`);
}

setStatus('pending');     // OK
setStatus('working');     // ERROR — not a Status

No runtime cost — these are just strings at runtime, validated at compile time. The de facto modern pattern.

as const — making literals literal

Without as const, TypeScript widens literal values:

const direction = 'north';        // type: 'north' (narrow — const declaration)
const directions = ['n', 's'];    // type: string[] (widened)

const dirsLit = ['n', 's'] as const;
// type: readonly ['n', 's']

const config = {
    method: 'GET',
    timeout: 5000
} as const;
// type: { readonly method: 'GET'; readonly timeout: 5000 }

as const freezes the value AND keeps each literal narrow. Useful for deriving types from data:

const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE'] as const;
type HttpMethod = typeof HTTP_METHODS[number];   // 'GET' | 'POST' | 'PUT' | 'DELETE'

Single source of truth — change the array, the type follows.

Numeric literal types

type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;

function roll(): DiceRoll {
    return (Math.floor(Math.random() * 6) + 1) as DiceRoll;
}

The cast is needed because TypeScript can't prove the math result is in 1-6 without help.

Boolean literal types

Less common but useful for state machines:

type LoadingState =
    | { loading: true }
    | { loading: false; data: string };

enum — the legacy approach

enum Color {
    Red,         // 0
    Green,       // 1
    Blue         // 2
}

const c: Color = Color.Red;
console.log(c);            // 0

Auto-numbered. Override:

enum HttpStatus {
    OK = 200,
    NotFound = 404,
    ServerError = 500
}

String enums:

enum Direction {
    North = 'N',
    South = 'S',
    East = 'E',
    West = 'W'
}

The catch with enum:

  • Generates a runtime object — bigger bundle
  • Numeric enums permit any number value (const c: Color = 99 compiles)
  • Doesn't work as well with bundler tree-shaking
  • const enum (inline values) has its own caveats and is disabled in many tsconfigs

TypeScript's modern guidance: use literal-type unions or as const objects for new code:

const Direction = {
    North: 'N',
    South: 'S',
    East: 'E',
    West: 'W'
} as const;

type Direction = typeof Direction[keyof typeof Direction];   // 'N' | 'S' | 'E' | 'W'

Same functionality, no runtime overhead beyond the object itself, plays well with bundlers.

Common mistakes

  • Reaching for enum reflexively — literal unions are usually better in modern code.
  • Numeric enums accepting any number — type system gap. Use string enums or literal unions for stricter checks.
  • Forgetting as const when wanting narrow types from object literals — the compiler widens to general string/number.
  • Mixing enum values across imports — enum identity depends on declaration. Two equivalent enums in different files are different types.
  • const enum in projects with isolated modules — often disabled by tsconfig because they don't work cleanly across module boundaries.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…