typescript-advanced-types ยท diff
git:20260307.47a5dbc to git:20260522.be57c0b
2 added, 401 removed. Audit A to A.
---
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<T>(value: T): T {
return value;
}
const num = identity<number>(42); // Type: number
const str = identity<string>("hello"); // Type: string
const auto = identity(true); // Type inferred: boolean
```
**Generic Constraints:**
```typescript
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(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<T, U>(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> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
```
**Extracting Return Types:**
```typescript
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser() {
return { id: 1, name: "John" };
}
type User = ReturnType<typeof getUser>;
// Type: { id: number; name: string; }
```
**Distributive Conditional Types:**
```typescript
type ToArray<T> = T extends any ? T[] : never;
type StrOrNumArray = ToArray<string | number>;
// Type: string[] | number[]
```
**Nested Conditions:**
```typescript
type TypeName<T> = 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>; // "string"
type T2 = TypeName<() => void>; // "function"
```
### 3. Mapped Types
**Purpose:** Transform existing types by iterating over their properties.
**Basic Mapped Type:**
```typescript
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface User {
id: number;
name: string;
}
type ReadonlyUser = Readonly<User>;
// Type: { readonly id: number; readonly name: string; }
```
**Optional Properties:**
```typescript
type Partial<T> = {
[P in keyof T]?: T[P];
};
type PartialUser = Partial<User>;
// Type: { id?: number; name?: string; }
```
**Key Remapping:**
```typescript
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
// Type: { getName: () => string; getAge: () => number; }
```
**Filtering Properties:**
```typescript
type PickByType<T, U> = {
[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<Mixed, number>;
// 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<EventName>}`;
// 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> = T extends object
? {
[K in keyof T]: K extends string ? `${K}` | `${K}.${Path<T[K]>}` : never;
}[keyof T]
: never;
interface Config {
server: {
host: string;
port: number;
};
database: {
url: string;
};
}
type ConfigPath = Path<Config>;
// Type: "server" | "database" | "server.host" | "server.port" | "database.url"
```
### 5. Utility Types
**Built-in Utility Types:**
```typescript
// Partial<T> - Make all properties optional
type PartialUser = Partial<User>;
// Required<T> - Make all properties required
type RequiredUser = Required<PartialUser>;
// Readonly<T> - Make all properties readonly
type ReadonlyUser = Readonly<User>;
// Pick<T, K> - Select specific properties
type UserName = Pick<User, "name" | "email">;
// Omit<T, K> - Remove specific properties
type UserWithoutPassword = Omit<User, "password">;
// Exclude<T, U> - Exclude types from union
type T1 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
// Extract<T, U> - Extract types from union
type T2 = Extract<"a" | "b" | "c", "a" | "b">; // "a" | "b"
// NonNullable<T> - Exclude null and undefined
type T3 = NonNullable<string | null | undefined>; // string
// Record<K, T> - 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<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): void {
- if (!this.listeners[event]) {
- this.listeners[event] = [];
- }
- this.listeners[event]!.push(callback);
- }
-
- emit<K extends keyof T>(event: K, data: T[K]): void {
- const callbacks = this.listeners[event];
- if (callbacks) {
- callbacks.forEach((callback) => callback(data));
- }
- }
- }
-
- const emitter = new TypedEventEmitter<EventMap>();
-
- 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<User>; response: User };
- DELETE: { params: { id: string }; response: void };
- };
- };
-
- type ExtractParams<T> = T extends { params: infer P } ? P : never;
- type ExtractBody<T> = T extends { body: infer B } ? B : never;
- type ExtractResponse<T> = T extends { response: infer R } ? R : never;
-
- class APIClient<Config extends Record<string, Record<HTTPMethod, any>>> {
- async request<Path extends keyof Config, Method extends keyof Config[Path]>(
- path: Path,
- method: Method,
- ...[options]: ExtractParams<Config[Path][Method]> extends never
- ? ExtractBody<Config[Path][Method]> extends never
- ? []
- : [{ body: ExtractBody<Config[Path][Method]> }]
- : [
- {
- params: ExtractParams<Config[Path][Method]>;
- body?: ExtractBody<Config[Path][Method]>;
- },
- ]
- ): Promise<ExtractResponse<Config[Path][Method]>> {
- // Implementation here
- return {} as any;
- }
- }
-
- const api = new APIClient<EndpointConfig>();
-
- // 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<T> = {
- [K in keyof T]: T[K] | undefined;
- };
-
- type RequiredKeys<T> = {
- [K in keyof T]-?: {} extends Pick<T, K> ? never : K;
- }[keyof T];
-
- type OptionalKeys<T> = {
- [K in keyof T]-?: {} extends Pick<T, K> ? K : never;
- }[keyof T];
-
- type IsComplete<T, S> =
- RequiredKeys<T> extends keyof S
- ? S[RequiredKeys<T>] extends undefined
- ? false
- : true
- : false;
-
- class Builder<T, S extends BuilderState<T> = {}> {
- private state: S = {} as S;
-
- set<K extends keyof T>(key: K, value: T[K]): Builder<T, S & Record<K, T[K]>> {
- this.state[key] = value;
- return this as any;
- }
-
- build(this: IsComplete<T, S> extends true ? this : never): T {
- return this.state as T;
- }
- }
-
- interface User {
- id: string;
- name: string;
- email: string;
- age?: number;
- }
-
- const builder = new Builder<User>();
-
- 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<T> = {
- readonly [P in keyof T]: T[P] extends object
- ? T[P] extends Function
- ? T[P]
- : DeepReadonly<T[P]>
- : T[P];
- };
-
- type DeepPartial<T> = {
- [P in keyof T]?: T[P] extends object
- ? T[P] extends Array<infer U>
- ? Array<DeepPartial<U>>
- : DeepPartial<T[P]>
- : 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<Config>;
- // All nested properties are readonly
-
- type PartialConfig = DeepPartial<Config>;
- // All nested properties are optional
- ```
-
- ### Pattern 5: Type-Safe Form Validation
-
- ```typescript
- type ValidationRule<T> = {
- validate: (value: T) => boolean;
- message: string;
- };
-
- type FieldValidation<T> = {
- [K in keyof T]?: ValidationRule<T[K]>[];
- };
-
- type ValidationErrors<T> = {
- [K in keyof T]?: string[];
- };
-
- class FormValidator<T extends Record<string, any>> {
- constructor(private rules: FieldValidation<T>) {}
-
- validate(data: T): ValidationErrors<T> | null {
- const errors: ValidationErrors<T> = {};
- 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<LoginForm>({
- 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<T> = {
- status: "success";
- data: T;
- };
-
- type Error = {
- status: "error";
- error: string;
- };
-
- type Loading = {
- status: "loading";
- };
-
- type AsyncState<T> = Success<T> | Error | Loading;
-
- function handleState<T>(state: AsyncState<T>): 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> = T extends (infer U)[] ? U : never;
-
- type NumArray = number[];
- type Num = ElementType<NumArray>; // number
-
- // Extract promise type
- type PromiseType<T> = T extends Promise<infer U> ? U : never;
-
- type AsyncNum = PromiseType<Promise<number>>; // number
-
- // Extract function parameters
- type Parameters<T> = T extends (...args: infer P) => any ? P : never;
-
- function foo(a: string, b: number) {}
- type FooParams = Parameters<typeof foo>; // [string, number]
- ```
-
- ### 2. Type Guards
-
- ```typescript
- function isString(value: unknown): value is string {
- return typeof value === "string";
- }
-
- function isArrayOf<T>(
- 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");
- }
- }
+ ## Detailed worked examples and patterns
- function processValue(value: unknown) {
- assertIsString(value);
- // value is now typed as string
- console.log(value.toUpperCase());
- }
- ```
+ Detailed sections (starting with `## Advanced Patterns`) live in `references/details.md`. Read that file when the navigation summary above is insufficient.
## 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, U> = [T] extends [U]
? [U] extends [T]
? true
: false
: false;
type Test1 = AssertEqual<string, string>; // true
type Test2 = AssertEqual<string, number>; // false
type Test3 = AssertEqual<string | number, string>; // false
// Expect error helper
type ExpectError<T extends never> = T;
// Example usage
type ShouldError = ExpectError<AssertEqual<string, number>>;
```
## 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