Reading — step 1 of 7
Learn
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 = 99compiles) - 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 constwhen 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 enumin 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…