TypeScript Advanced Types: satisfies, Narrowing, and Branded Types
Move beyond basic interfaces with TypeScript satisfies operator, discriminated unions, template literal types, and branded types for safer domain modeling in large codebases.
When Interfaces Are Not Enough
TypeScript's structural typing is powerful but can erode precision: widening literals, losing autocomplete on const objects, and allowing invalid states that compile silently. Modern TypeScript (4.9+) adds tools that preserve developer ergonomics while tightening correctness.
The satisfies Operator
satisfies validates that an expression matches a type without widening it to that type. You keep literal inference and still catch missing or extra keys.
type Route = Record<string, { path: string; method: 'GET' | 'POST' }>;
const routes = {
home: { path: '/', method: 'GET' },
login: { path: '/login', method: 'POST' },
} satisfies Route;
// routes.home.method is 'GET' | 'POST', not string
// Typo in key or method is caught at compile time
Discriminated Unions for State Machines
Model UI and API states with a shared discriminant field (kind, status, type). Control flow narrowing eliminates impossible branches without runtime checks scattered everywhere.
type AsyncState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
function render<T>(state: AsyncState<T>) {
switch (state.status) {
case 'success': return state.data; // narrowed
case 'error': return state.error.message;
default: return null;
}
}
Branded Types for Domain Safety
Prevent mixing primitive IDs at compile time by branding strings:
type UserId = string & { readonly brand: unique symbol };
type OrderId = string & { readonly brand: unique symbol };
function getUser(id: UserId) { /* ... */ }
// getUser(orderId) // compile error
Template Literal Types
Generate CSS property unions, event names, or API route strings from const tuples. Pair with as const for design-system tokens and typed environment variable maps.
Advanced TypeScript is not academic. It reduces production bugs, improves IDE navigation, and documents domain rules in code. Invest in types at boundaries: API payloads, config objects, and shared state shapes.