typescript-advanced
Write advanced TypeScript with full type safety. Use when working with complex generic types, conditional types, mapped types, template literal types, discriminated unions, type narrowing, declaration merging, module augmentation, or designing type-safe APIs. Covers TypeScript 5.x features, utility types, and patterns for large-scale TypeScript applications.
Advanced TypeScript Type System
TypeScript Mastery = Understanding the Type Level
TypeScript has TWO levels:
- Value level: Runtime JavaScript code
- Type level: Compile-time type transformations (almost a separate language)
Master both to write truly type-safe code.
Generic Constraints and Inference
// Basic generics
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
// Constrained generics
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// Infer return type from function
function identity<T>(value: T): T {
return value;
}
// Multiple type parameters with relationships
function merge<T extends object, U extends object>(a: T, b: U): T & U {
return { ...a, ...b };
}
// Generic with default type
function createState<T = string>(initial: T) {
let state = initial;
return {
get: () => state,
set: (value: T) => { state = value; },
};
}
// Conditional inference
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
type Result = UnpackPromise<Promise<string>>; // string
type Same = UnpackPromise<number>; // number
Conditional Types
// Basic conditional
type IsArray<T> = T extends any[] ? true : false;
type A = IsArray<string[]>; // true
type B = IsArray<string>; // false
// Distributive conditional (applied to each union member)
type ToArray<T> = T extends any ? T[] : never;
type StringOrNumber = ToArray<string | number>; // string[] | number[]
// Non-distributive (wrap in [])
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type Flat = ToArrayNonDist<string | number>; // (string | number)[]
// Extract and Exclude (built-in, but show implementation)
type MyExtract<T, U> = T extends U ? T : never;
type MyExclude<T, U> = T extends U ? never : T;
// Complex conditional
type DeepReadonly<T> = T extends (infer U)[]
? DeepReadonlyArray<U>
: T extends object
? DeepReadonlyObject<T>
: T;
interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}
type DeepReadonlyObject<T> = {
readonly [P in keyof T]: DeepReadonly<T[P]>;
};
// Usage
const config: DeepReadonly<{
db: { host: string; port: number };
features: string[];
}> = {
db: { host: 'localhost', port: 5432 },
features: ['auth', 'search'],
};
// config.db.host = 'other'; // Error!
// config.features.push('x'); // Error!
Mapped Types
// Built-in utility types (understand what's underneath)
type Partial<T> = { [K in keyof T]?: T[K] };
type Required<T> = { [K in keyof T]-?: T[K] }; // -? removes optional
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Record<K extends keyof any, T> = { [P in K]: T };
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
// Custom mapped types
// Make all functions in an object async
type AsyncMethods<T> = {
[K in keyof T]: T[K] extends (...args: infer A) => infer R
? (...args: A) => Promise<R>
: T[K];
};
// Prefix all keys
type Prefixed<T, P extends string> = {
[K in keyof T as `${P}${Capitalize<string & K>}`]: T[K];
};
type WithGet<T> = Prefixed<T, 'get'>;
// { getName: ..., getAge: ... } if T has name and age
// Filter by value type
type FilterByValue<T, V> = {
[K in keyof T as T[K] extends V ? K : never]: T[K];
};
interface User {
id: number;
name: string;
email: string;
age: number;
}
type StringFields = FilterByValue<User, string>; // { name: string; email: string }
// Builder pattern with mapped types
type Builder<T> = {
[K in keyof T]-?: (value: T[K]) => Builder<T>;
} & { build(): T };
Template Literal Types
// Build complex string types
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickHandler = EventName<'click'>; // "onClick"
// HTTP methods
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
type ApiRoute = `/${string}`;
type ApiEndpoint = `${HttpMethod} ${ApiRoute}`;
const endpoint: ApiEndpoint = 'GET /users/123';
// CSS properties
type CSSValue = `${number}px` | `${number}em` | `${number}rem` | `${number}%`;
const width: CSSValue = '100px';
// Parse route parameters
type ExtractRouteParams<T extends string> =
string extends T ? Record<string, string> :
T extends `${infer _}:${infer Param}/${infer Rest}`
? { [K in Param | keyof ExtractRouteParams<Rest>]: string }
: T extends `${infer _}:${infer Param}`
? { [K in Param]: string }
: {};
type Params = ExtractRouteParams<'/users/:userId/posts/:postId'>;
// { userId: string; postId: string }
// Deep property access
type DeepGet<T, Path extends string> =
Path extends `${infer Key}.${infer Rest}`
? Key extends keyof T
? DeepGet<T[Key], Rest>
: never
: Path extends keyof T
? T[Path]
: never;
type Config = { db: { host: string; port: number } };
type DBHost = DeepGet<Config, 'db.host'>; // string
Discriminated Unions (Type-Safe State Machines)
// Every state machine should be a discriminated union
type RequestState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
function renderUser(state: RequestState<User>): JSX.Element {
switch (state.status) {
case 'idle':
return <div>Not started</div>;
case 'loading':
return <Spinner />;
case 'success':
return <UserCard user={state.data} />; // data is typed as User
case 'error':
return <Error message={state.error.message} />; // error is typed
}
}
// Exhaustive check — TypeScript will error if a case is missing
function assertNever(x: never): never {
throw new Error(`Unexpected value: ${x}`);
}
// Result type (Rust-inspired)
type Ok<T> = { success: true; value: T };
type Err<E> = { success: false; error: E };
type Result<T, E = Error> = Ok<T> | Err<E>;
function divide(a: number, b: number): Result<number, string> {
if (b === 0) return { success: false, error: 'Division by zero' };
return { success: true, value: a / b };
}
const result = divide(10, 2);
if (result.success) {
console.log(result.value); // number
} else {
console.log(result.error); // string
}
Declaration Merging and Module Augmentation
// Extend third-party types
// Add custom properties to Express Request
declare global {
namespace Express {
interface Request {
user?: AuthUser;
requestId: string;
}
}
}
// Extend existing interfaces
interface Window {
analytics: Analytics;
__ENV__: Record<string, string>;
}
// Module augmentation — add methods to existing module
declare module 'express-serve-static-core' {
interface Request {
correlationId?: string;
}
}
// Extend env variables
declare namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string;
REDIS_URL: string;
PORT?: string;
NODE_ENV: 'development' | 'production' | 'test';
}
}
// Now process.env.DATABASE_URL is typed as string (not string | undefined)
Type-Safe Event System
// Event map pattern — fully type-safe event emitter
type EventMap = {
'user:created': { id: string; email: string };
'user:deleted': { id: string };
'order:placed': { orderId: string; userId: string; amount: number };
'payment:failed': { orderId: string; error: string };
};
class TypedEventEmitter<Events extends Record<string, unknown>> {
private handlers = new Map<keyof Events, Set<(payload: any) => void>>();
on<E extends keyof Events>(
event: E,
handler: (payload: Events[E]) => void
): () => void {
if (!this.handlers.has(event)) {
this.handlers.set(event, new Set());
}
this.handlers.get(event)!.add(handler);
// Return unsubscribe function
return () => this.handlers.get(event)?.delete(handler);
}
emit<E extends keyof Events>(event: E, payload: Events[E]): void {
this.handlers.get(event)?.forEach(handler => handler(payload));
}
}
const emitter = new TypedEventEmitter<EventMap>();
// Fully typed — autocomplete on event names and payload
const unsub = emitter.on('user:created', (payload) => {
console.log(payload.id, payload.email); // typed!
});
emitter.emit('order:placed', {
orderId: 'ord-1',
userId: 'usr-1',
amount: 99.99, // Must match the type exactly
});
Utility Type Cookbook
// Common patterns you'll use repeatedly
// Make specific keys required
type RequiredKeys<T, K extends keyof T> = T & Required<Pick<T, K>>;
// Make specific keys optional
type OptionalKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
// Flatten nested types for display
type Flatten<T> = { [K in keyof T]: T[K] };
// Merge two types (second overrides first)
type Merge<T, U> = Flatten<Omit<T, keyof U> & U>;
// Get keys of specific value type
type KeysOfType<T, V> = {
[K in keyof T]: T[K] extends V ? K : never;
}[keyof T];
type StringKeys = KeysOfType<User, string>; // "name" | "email"
// Non-nullable
type NonNullableDeep<T> = T extends null | undefined
? never
: T extends object
? { [K in keyof T]: NonNullableDeep<T[K]> }
: T;
// Tuple operations
type Head<T extends any[]> = T extends [infer H, ...any[]] ? H : never;
type Tail<T extends any[]> = T extends [any, ...infer T] ? T : never;
type Length<T extends any[]> = T['length'];
type Concat<A extends any[], B extends any[]> = [...A, ...B];
// Function composition types
type Compose<F, G> =
F extends (...args: any[]) => infer R
? G extends (arg: R) => infer S
? S
: never
: never;
satisfies Operator (TypeScript 4.9+)
// Problem: Type annotation loses specific type information
const config: Record<string, string | number> = {
host: 'localhost',
port: 5432,
};
config.host.toUpperCase(); // Error! TypeScript thinks it might be number
// satisfies: validate type without widening
const config2 = {
host: 'localhost',
port: 5432,
} satisfies Record<string, string | number>;
config2.host.toUpperCase(); // Works! TypeScript knows it's a string
config2.port.toFixed(2); // Works! TypeScript knows it's a number
// Validate palette colors
type Color = `#${string}` | [number, number, number];
const palette = {
primary: '#3b82f6',
secondary: [239, 68, 68], // RGB tuple
accent: '#10b981',
} satisfies Record<string, Color>;
palette.primary.startsWith('#'); // Works (string)
palette.secondary[0].toFixed(); // Works (number)
tsconfig.json Best Practices
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM"],
"module": "Node16", // Modern Node.js ESM
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
// Strict mode — turn on ALL of these
"strict": true, // Enables all strict checks below
"noUncheckedIndexedAccess": true, // array[0] returns T | undefined
"exactOptionalPropertyTypes": true, // Optional != | undefined
"noPropertyAccessFromIndexSignature": true,
// Additional safety
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"forceConsistentCasingInFileNames": true,
// Output
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}Skill Information
- Source
- MoltbotDen
- Category
- Coding Agents & IDEs
- Repository
- View on GitHub
Related Skills
go-expert
Write idiomatic, production-quality Go code. Use when building Go APIs, CLIs, microservices, or systems code. Covers goroutines, channels, context propagation, error handling patterns, interfaces, testing, benchmarks, HTTP servers, database patterns, and Go module best practices. Expert-level Go idioms that senior engineers expect.
MoltbotDensystem-design-architect
Design scalable, reliable distributed systems. Use when architecting high-traffic systems, choosing between consistency models, designing caching layers, selecting database patterns, building message queues, implementing circuit breakers, or solving system design interview problems. Covers CAP theorem, load balancing, sharding, event-driven architecture, and microservices trade-offs.
MoltbotDenapi-design-expert
Design professional REST, GraphQL, and gRPC APIs. Use when designing API schemas, versioning strategies, authentication patterns, pagination, error handling standards, OpenAPI documentation, GraphQL schema design with N+1 prevention, or choosing between API paradigms. Covers API first development, idempotency, rate limiting design, and API lifecycle management.
MoltbotDenrust-systems
Write safe, performant Rust systems code. Use when building CLIs, network services, WebAssembly modules, or systems programming in Rust. Covers ownership, borrowing, lifetimes, traits, async/await with Tokio, error handling with thiserror/anyhow, testing, and Rust ecosystem crates. Idiomatic Rust patterns that pass code review.
MoltbotDengraphql-expert
Design and implement production GraphQL APIs. Use when designing GraphQL schemas, implementing resolvers, solving N+1 problems with DataLoader, implementing subscriptions, building GraphQL federation, generating types from schemas, or optimizing GraphQL performance. Covers Apollo Server, GraphQL Yoga, Pothos schema builder, and persisted queries.
MoltbotDen