TypeScript Type Safety Expert
Advanced TypeScript patterns for bulletproof type safety.
Advanced Type Patterns
Branded Types
// Prevent mixing incompatible types type Brand<K, T> = K & { __brand: T };
type UserId = Brand<string, 'UserId'>; type ProductId = Brand<string, 'ProductId'>;
const userId = 'user_123' as UserId; const productId = 'prod_456' as ProductId;
function getUser(id: UserId) { /* ... */ }
getUser(userId); // ✅ OK getUser(productId); // ❌ Type error!
Discriminated Unions
type Success<T> = { success: true; data: T }; type Error = { success: false; error: string }; type Result<T> = Success<T> | Error;
function handleResult<T>(result: Result<T>) { if (result.success) { // TypeScript knows result.data exists console.log(result.data); } else { // TypeScript knows result.error exists console.error(result.error); } }
Template Literal Types
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Route = /${string};
type Endpoint = ${HttpMethod} ${Route};
const endpoint: Endpoint = 'GET /users'; // ✅ const invalid: Endpoint = 'FETCH /data'; // ❌ Type error
// Dynamic key generation
type EventName = on${Capitalize<string>};
const onClick: EventName = 'onClick'; // ✅
const invalid: EventName = 'click'; // ❌
Recursive Types
type JSONValue = | string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };
const validJSON: JSONValue = { name: "Alice", age: 30, tags: ["developer", "typescript"], metadata: { created: "2024-01-01", nested: { deep: true } } };
Utility Type Combinations
// Make all properties optional recursively type DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]; };
// Make specific keys required type RequireKeys<T, K extends keyof T> = T & Required<Pick<T, K>>;
// Exclude null and undefined type NonNullableKeys<T> = { [P in keyof T]: NonNullable<T[P]>; };
// Extract function parameters type Params<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
Type-Safe API Client
type API = { '/users': { GET: { response: User[] }; POST: { body: UserCreate; response: User }; }; '/users/:id': { GET: { params: { id: string }; response: User }; PUT: { params: { id: string }; body: UserUpdate; response: User }; DELETE: { params: { id: string }; response: void }; }; };
type ExtractParams<T extends string> =
T extends ${infer _Start}:${infer Param}/${infer Rest}
? { [K in Param | keyof ExtractParams<Rest>]: string }
: T extends ${infer _}:${infer Param}
? { [K in Param]: string }
: {};
async function apiCall< Path extends keyof API, Method extends keyof API[Path]
( method: Method, path: Path, options?: API[Path][Method] extends { body: infer B } ? { body: B; params?: ExtractParams<Path> } : { params?: ExtractParams<Path> } ): Promise<API[Path][Method] extends { response: infer R } ? R : never> { // Implementation return {} as any; }
// Usage - fully type-safe! const user = await apiCall('GET', '/users/:id', { params: { id: '123' } // ✅ Required });
const newUser = await apiCall('POST', '/users', { body: { email: 'test@test.com', name: 'Test' } // ✅ Required });
Builder Pattern with Type State
class QueryBuilder<T extends Record<string, any>, HasWhere = false> { private whereClause?: string;
where<K extends keyof T>(key: K, value: T[K]): QueryBuilder<T, true> {
this.whereClause = ${String(key)} = ${value};
return this as any;
}
// execute() only available after where() is called execute(this: QueryBuilder<T, true>): Promise<T[]> { return Promise.resolve([]); } }
const query = new QueryBuilder<User>(); query.execute(); // ❌ Type error - must call where() first query.where('id', 123).execute(); // ✅ OK
Strict Event Emitter
type EventMap = { 'user:created': { id: string; name: string }; 'user:deleted': { id: string }; 'data:update': { data: any[] }; };
class TypedEventEmitter<T extends Record<string, any>> { private listeners: { [K in keyof T]?: Array<(data: T[K]) => void>; } = {};
on<K extends keyof T>(event: K, callback: (data: T[K]) => void) { if (!this.listeners[event]) { this.listeners[event] = []; } this.listeners[event]!.push(callback); }
emit<K extends keyof T>(event: K, data: T[K]) { this.listeners[event]?.forEach(cb => cb(data)); } }
const emitter = new TypedEventEmitter<EventMap>();
emitter.on('user:created', (data) => { console.log(data.id, data.name); // ✅ Fully typed });
emitter.emit('user:created', { id: '1', name: 'Alice' }); // ✅ OK emitter.emit('user:created', { wrong: 'data' }); // ❌ Type error
Zod Integration
import { z } from 'zod';
const UserSchema = z.object({ id: z.string().uuid(), email: z.string().email(), age: z.number().min(0).max(150), role: z.enum(['admin', 'user', 'guest']), metadata: z.record(z.unknown()).optional(), });
type User = z.infer<typeof UserSchema>;
// Runtime validation with compile-time types function validateUser(data: unknown): User { return UserSchema.parse(data); }
TSConfig Best Practices
{ "compilerOptions": { "strict": true, "exactOptionalPropertyTypes": true, "noUncheckedIndexedAccess": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noFallthroughCasesInSwitch": true, "allowUnusedLabels": false, "allowUnreachableCode": false, "forceConsistentCasingInFileNames": true, "skipLibCheck": true } }
Quick Patterns
// Const assertions const config = { apiUrl: 'https://api.example.com', timeout: 5000, } as const; // Type: { readonly apiUrl: "https://api.example.com"; readonly timeout: 5000 }
// Satisfies operator const colors = { red: [255, 0, 0], green: [0, 255, 0], } satisfies Record<string, [number, number, number]>;
// Index signatures with template literals
type HTTPHeaders = {
[K in x-${string}]: string;
};
// Conditional types type IsArray<T> = T extends any[] ? true : false; type Test1 = IsArray<string[]>; // true type Test2 = IsArray<string>; // false
When to Use: Advanced TypeScript features, eliminating runtime errors, type-safe APIs, complex type systems.