--- name: typescript-advanced-types description: Master TypeScript's advanced type system including generics, conditional types, mapped types, template literals, and utility types for building type-safe applications. Use when implementing complex type logic, creating reusable type utilities, or ensuring compile-time type safety in TypeScript projects. --- # TypeScript Advanced Types Comprehensive guidance for mastering TypeScript's advanced type system including generics, conditional types, mapped types, template literal types, and utility types for building robust, type-safe applications. ## When to Use This Skill - Building type-safe libraries or frameworks - Creating reusable generic components - Implementing complex type inference logic - Designing type-safe API clients - Building form validation systems - Creating strongly-typed configuration objects - Implementing type-safe state management - Migrating JavaScript codebases to TypeScript ## Core Concepts ### 1. Generics **Purpose:** Create reusable, type-flexible components while maintaining type safety. **Basic Generic Function:** ```typescript function identity(value: T): T { return value; } const num = identity(42); // Type: number const str = identity("hello"); // Type: string const auto = identity(true); // Type inferred: boolean ``` **Generic Constraints:** ```typescript interface HasLength { length: number; } function logLength(item: T): T { console.log(item.length); return item; } logLength("hello"); // OK: string has length logLength([1, 2, 3]); // OK: array has length logLength({ length: 10 }); // OK: object has length // logLength(42); // Error: number has no length ``` **Multiple Type Parameters:** ```typescript function merge(obj1: T, obj2: U): T & U { return { ...obj1, ...obj2 }; } const merged = merge( { name: "John" }, { age: 30 } ); // Type: { name: string } & { age: number } ``` ### 2. Conditional Types **Purpose:** Create types that depend on conditions, enabling sophisticated type logic. **Basic Conditional Type:** ```typescript type IsString = T extends string ? true : false; type A = IsString; // true type B = IsString; // false ``` **Extracting Return Types:** ```typescript type ReturnType = T extends (...args: any[]) => infer R ? R : never; function getUser() { return { id: 1, name: "John" }; } type User = ReturnType; // Type: { id: number; name: string; } ``` **Distributive Conditional Types:** ```typescript type ToArray = T extends any ? T[] : never; type StrOrNumArray = ToArray; // Type: string[] | number[] ``` **Nested Conditions:** ```typescript type TypeName = T extends string ? "string" : T extends number ? "number" : T extends boolean ? "boolean" : T extends undefined ? "undefined" : T extends Function ? "function" : "object"; type T1 = TypeName; // "string" type T2 = TypeName<() => void>; // "function" ``` ### 3. Mapped Types **Purpose:** Transform existing types by iterating over their properties. **Basic Mapped Type:** ```typescript type Readonly = { readonly [P in keyof T]: T[P]; }; interface User { id: number; name: string; } type ReadonlyUser = Readonly; // Type: { readonly id: number; readonly name: string; } ``` **Optional Properties:** ```typescript type Partial = { [P in keyof T]?: T[P]; }; type PartialUser = Partial; // Type: { id?: number; name?: string; } ``` **Key Remapping:** ```typescript type Getters = { [K in keyof T as `get${Capitalize}`]: () => T[K] }; interface Person { name: string; age: number; } type PersonGetters = Getters; // Type: { getName: () => string; getAge: () => number; } ``` **Filtering Properties:** ```typescript type PickByType = { [K in keyof T as T[K] extends U ? K : never]: T[K] }; interface Mixed { id: number; name: string; age: number; active: boolean; } type OnlyNumbers = PickByType; // Type: { id: number; age: number; } ``` ### 4. Template Literal Types **Purpose:** Create string-based types with pattern matching and transformation. **Basic Template Literal:** ```typescript type EventName = "click" | "focus" | "blur"; type EventHandler = `on${Capitalize}`; // Type: "onClick" | "onFocus" | "onBlur" ``` **String Manipulation:** ```typescript type UppercaseGreeting = Uppercase<"hello">; // "HELLO" type LowercaseGreeting = Lowercase<"HELLO">; // "hello" type CapitalizedName = Capitalize<"john">; // "John" type UncapitalizedName = Uncapitalize<"John">; // "john" ``` **Path Building:** ```typescript type Path = T extends object ? { [K in keyof T]: K extends string ? `${K}` | `${K}.${Path}` : never }[keyof T] : never; interface Config { server: { host: string; port: number; }; database: { url: string; }; } type ConfigPath = Path; // Type: "server" | "database" | "server.host" | "server.port" | "database.url" ``` ### 5. Utility Types **Built-in Utility Types:** ```typescript // Partial - Make all properties optional type PartialUser = Partial; // Required - Make all properties required type RequiredUser = Required; // Readonly - Make all properties readonly type ReadonlyUser = Readonly; // Pick - Select specific properties type UserName = Pick; // Omit - Remove specific properties type UserWithoutPassword = Omit; // Exclude - Exclude types from union type T1 = Exclude<"a" | "b" | "c", "a">; // "b" | "c" // Extract - Extract types from union type T2 = Extract<"a" | "b" | "c", "a" | "b">; // "a" | "b" // NonNullable - Exclude null and undefined type T3 = NonNullable; // string // Record - Create object type with keys K and values T type PageInfo = Record<"home" | "about", { title: string }>; ``` ## Advanced Patterns ### Pattern 1: Type-Safe Event Emitter ```typescript type EventMap = { "user:created": { id: string; name: string }; "user:updated": { id: string }; "user:deleted": { id: string }; }; class TypedEventEmitter> { private listeners: { [K in keyof T]?: Array<(data: T[K]) => void>; } = {}; on(event: K, callback: (data: T[K]) => void): void { if (!this.listeners[event]) { this.listeners[event] = []; } this.listeners[event]!.push(callback); } emit(event: K, data: T[K]): void { const callbacks = this.listeners[event]; if (callbacks) { callbacks.forEach(callback => callback(data)); } } } const emitter = new TypedEventEmitter(); emitter.on("user:created", (data) => { console.log(data.id, data.name); // Type-safe! }); emitter.emit("user:created", { id: "1", name: "John" }); // emitter.emit("user:created", { id: "1" }); // Error: missing 'name' ``` ### Pattern 2: Type-Safe API Client ```typescript type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE"; type EndpointConfig = { "/users": { GET: { response: User[] }; POST: { body: { name: string; email: string }; response: User }; }; "/users/:id": { GET: { params: { id: string }; response: User }; PUT: { params: { id: string }; body: Partial; response: User }; DELETE: { params: { id: string }; response: void }; }; }; type ExtractParams = T extends { params: infer P } ? P : never; type ExtractBody = T extends { body: infer B } ? B : never; type ExtractResponse = T extends { response: infer R } ? R : never; class APIClient>> { async request< Path extends keyof Config, Method extends keyof Config[Path] >( path: Path, method: Method, ...[options]: ExtractParams extends never ? ExtractBody extends never ? [] : [{ body: ExtractBody }] : [{ params: ExtractParams; body?: ExtractBody; }] ): Promise> { // Implementation here return {} as any; } } const api = new APIClient(); // Type-safe API calls const users = await api.request("/users", "GET"); // Type: User[] const newUser = await api.request("/users", "POST", { body: { name: "John", email: "john@example.com" } }); // Type: User const user = await api.request("/users/:id", "GET", { params: { id: "123" } }); // Type: User ``` ### Pattern 3: Builder Pattern with Type Safety ```typescript type BuilderState = { [K in keyof T]: T[K] | undefined; }; type RequiredKeys = { [K in keyof T]-?: {} extends Pick ? never : K; }[keyof T]; type OptionalKeys = { [K in keyof T]-?: {} extends Pick ? K : never; }[keyof T]; type IsComplete = RequiredKeys extends keyof S ? S[RequiredKeys] extends undefined ? false : true : false; class Builder = {}> { private state: S = {} as S; set( key: K, value: T[K] ): Builder> { this.state[key] = value; return this as any; } build( this: IsComplete extends true ? this : never ): T { return this.state as T; } } interface User { id: string; name: string; email: string; age?: number; } const builder = new Builder(); const user = builder .set("id", "1") .set("name", "John") .set("email", "john@example.com") .build(); // OK: all required fields set // const incomplete = builder // .set("id", "1") // .build(); // Error: missing required fields ``` ### Pattern 4: Deep Readonly/Partial ```typescript type DeepReadonly = { readonly [P in keyof T]: T[P] extends object ? T[P] extends Function ? T[P] : DeepReadonly : T[P]; }; type DeepPartial = { [P in keyof T]?: T[P] extends object ? T[P] extends Array ? Array> : DeepPartial : T[P]; }; interface Config { server: { host: string; port: number; ssl: { enabled: boolean; cert: string; }; }; database: { url: string; pool: { min: number; max: number; }; }; } type ReadonlyConfig = DeepReadonly; // All nested properties are readonly type PartialConfig = DeepPartial; // All nested properties are optional ``` ### Pattern 5: Type-Safe Form Validation ```typescript type ValidationRule = { validate: (value: T) => boolean; message: string; }; type FieldValidation = { [K in keyof T]?: ValidationRule[]; }; type ValidationErrors = { [K in keyof T]?: string[]; }; class FormValidator> { constructor(private rules: FieldValidation) {} validate(data: T): ValidationErrors | null { const errors: ValidationErrors = {}; let hasErrors = false; for (const key in this.rules) { const fieldRules = this.rules[key]; const value = data[key]; if (fieldRules) { const fieldErrors: string[] = []; for (const rule of fieldRules) { if (!rule.validate(value)) { fieldErrors.push(rule.message); } } if (fieldErrors.length > 0) { errors[key] = fieldErrors; hasErrors = true; } } } return hasErrors ? errors : null; } } interface LoginForm { email: string; password: string; } const validator = new FormValidator({ email: [ { validate: (v) => v.includes("@"), message: "Email must contain @" }, { validate: (v) => v.length > 0, message: "Email is required" } ], password: [ { validate: (v) => v.length >= 8, message: "Password must be at least 8 characters" } ] }); const errors = validator.validate({ email: "invalid", password: "short" }); // Type: { email?: string[]; password?: string[]; } | null ``` ### Pattern 6: Discriminated Unions ```typescript type Success = { status: "success"; data: T; }; type Error = { status: "error"; error: string; }; type Loading = { status: "loading"; }; type AsyncState = Success | Error | Loading; function handleState(state: AsyncState): void { switch (state.status) { case "success": console.log(state.data); // Type: T break; case "error": console.log(state.error); // Type: string break; case "loading": console.log("Loading..."); break; } } // Type-safe state machine type State = | { type: "idle" } | { type: "fetching"; requestId: string } | { type: "success"; data: any } | { type: "error"; error: Error }; type Event = | { type: "FETCH"; requestId: string } | { type: "SUCCESS"; data: any } | { type: "ERROR"; error: Error } | { type: "RESET" }; function reducer(state: State, event: Event): State { switch (state.type) { case "idle": return event.type === "FETCH" ? { type: "fetching", requestId: event.requestId } : state; case "fetching": if (event.type === "SUCCESS") { return { type: "success", data: event.data }; } if (event.type === "ERROR") { return { type: "error", error: event.error }; } return state; case "success": case "error": return event.type === "RESET" ? { type: "idle" } : state; } } ``` ## Type Inference Techniques ### 1. Infer Keyword ```typescript // Extract array element type type ElementType = T extends (infer U)[] ? U : never; type NumArray = number[]; type Num = ElementType; // number // Extract promise type type PromiseType = T extends Promise ? U : never; type AsyncNum = PromiseType>; // number // Extract function parameters type Parameters = T extends (...args: infer P) => any ? P : never; function foo(a: string, b: number) {} type FooParams = Parameters; // [string, number] ``` ### 2. Type Guards ```typescript function isString(value: unknown): value is string { return typeof value === "string"; } function isArrayOf( value: unknown, guard: (item: unknown) => item is T ): value is T[] { return Array.isArray(value) && value.every(guard); } const data: unknown = ["a", "b", "c"]; if (isArrayOf(data, isString)) { data.forEach(s => s.toUpperCase()); // Type: string[] } ``` ### 3. Assertion Functions ```typescript function assertIsString(value: unknown): asserts value is string { if (typeof value !== "string") { throw new Error("Not a string"); } } function processValue(value: unknown) { assertIsString(value); // value is now typed as string console.log(value.toUpperCase()); } ``` ## Best Practices 1. **Use `unknown` over `any`**: Enforce type checking 2. **Prefer `interface` for object shapes**: Better error messages 3. **Use `type` for unions and complex types**: More flexible 4. **Leverage type inference**: Let TypeScript infer when possible 5. **Create helper types**: Build reusable type utilities 6. **Use const assertions**: Preserve literal types 7. **Avoid type assertions**: Use type guards instead 8. **Document complex types**: Add JSDoc comments 9. **Use strict mode**: Enable all strict compiler options 10. **Test your types**: Use type tests to verify type behavior ## Type Testing ```typescript // Type assertion tests type AssertEqual = [T] extends [U] ? [U] extends [T] ? true : false : false; type Test1 = AssertEqual; // true type Test2 = AssertEqual; // false type Test3 = AssertEqual; // false // Expect error helper type ExpectError = T; // Example usage type ShouldError = ExpectError>; ``` ## Common Pitfalls 1. **Over-using `any`**: Defeats the purpose of TypeScript 2. **Ignoring strict null checks**: Can lead to runtime errors 3. **Too complex types**: Can slow down compilation 4. **Not using discriminated unions**: Misses type narrowing opportunities 5. **Forgetting readonly modifiers**: Allows unintended mutations 6. **Circular type references**: Can cause compiler errors 7. **Not handling edge cases**: Like empty arrays or null values ## Performance Considerations - Avoid deeply nested conditional types - Use simple types when possible - Cache complex type computations - Limit recursion depth in recursive types - Use build tools to skip type checking in production ## Resources - **TypeScript Handbook**: https://www.typescriptlang.org/docs/handbook/ - **Type Challenges**: https://github.com/type-challenges/type-challenges - **TypeScript Deep Dive**: https://basarat.gitbook.io/typescript/ - **Effective TypeScript**: Book by Dan Vanderkam