Advanced TypeScript Patterns
Take your TypeScript skills to the next level with advanced patterns and techniques.
Generic Constraints and Utility Types
Conditional Types
typescript
// Basic conditional type
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
// More complex conditional type
type NonNullable<T> = T extends null | undefined ? never : T;
type Example1 = NonNullable<string | null>; // string
type Example2 = NonNullable<number | undefined>; // numberMapped Types
typescript
// Make all properties optional
type Partial<T> = {
[P in keyof T]?: T[P];
};
// Make all properties required
type Required<T> = {
[P in keyof T]-?: T[P];
};
// Pick specific properties
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
// Omit specific properties
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
// Custom mapped type
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};Advanced Generic Patterns
Generic Constraints
typescript
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
// Usage
loggingIdentity("hello"); // OK
loggingIdentity([1, 2, 3]); // OK
loggingIdentity(123); // Error: number doesn't have lengthGeneric Function Overloads
typescript
function createElement<T extends string>(
tagName: T
): HTMLElementTagNameMap[T];
function createElement<T extends keyof HTMLElementTagNameMap>(
tagName: T
): HTMLElementTagNameMap[T];
function createElement(tagName: string): HTMLElement {
return document.createElement(tagName);
}
// Usage with proper typing
const div = createElement('div'); // HTMLDivElement
const input = createElement('input'); // HTMLInputElementTemplate Literal Types
typescript
// Basic template literals
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<'click'>; // 'onClick'
type HoverEvent = EventName<'hover'>; // 'onHover'
// More complex example
type ApiEndpoint<T extends string> = `/api/${T}`;
type UserEndpoint = ApiEndpoint<'users'>; // '/api/users'
// CSS property names
type CSSProperty<T extends string> = `--${T}`;
type CustomProperty = CSSProperty<'primary-color'>; // '--primary-color'Advanced Utility Types
Recursive Types
typescript
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
// Deep readonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};Branded Types
typescript
// Create branded types for type safety
type UserId = string & { __brand: 'UserId' };
type ProductId = string & { __brand: 'ProductId' };
function createUserId(id: string): UserId {
return id as UserId;
}
function createProductId(id: string): ProductId {
return id as ProductId;
}
// Usage - prevents mixing up IDs
function getUser(id: UserId) {
// Implementation
}
const userId = createUserId('123');
const productId = createProductId('456');
getUser(userId); // OK
getUser(productId); // Error: ProductId is not assignable to UserIdAdvanced Function Types
Function Overloads with Generics
typescript
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// Overload signatures
function apiCall<T extends string>(endpoint: T): Promise<ApiResponse<any>>;
function apiCall<T extends string, R>(
endpoint: T,
data: R
): Promise<ApiResponse<R>>;
// Implementation
function apiCall<T extends string, R>(
endpoint: T,
data?: R
): Promise<ApiResponse<any>> {
// Implementation
return Promise.resolve({ data: data || {}, status: 200, message: 'OK' });
}Higher-Order Functions
typescript
// Function that returns a function with proper typing
function createValidator<T>(
schema: Record<keyof T, (value: any) => boolean>
) {
return (data: Partial<T>): data is T => {
return Object.keys(schema).every(key =>
schema[key as keyof T](data[key as keyof T])
);
};
}
// Usage
const userSchema = {
name: (value: any) => typeof value === 'string',
age: (value: any) => typeof value === 'number' && value > 0,
email: (value: any) => typeof value === 'string' && value.includes('@')
};
const validateUser = createValidator(userSchema);Module Augmentation
typescript
// Extend existing module types
declare global {
interface Window {
myCustomProperty: string;
}
}
// Augment existing modules
declare module 'express' {
interface Request {
user?: {
id: string;
email: string;
};
}
}Advanced Error Handling
typescript
// Result type for error handling
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
// Async result type
type AsyncResult<T, E = Error> = Promise<Result<T, E>>;
// Usage
async function fetchUser(id: string): AsyncResult<User> {
try {
const user = await api.getUser(id);
return { success: true, data: user };
} catch (error) {
return { success: false, error: error as Error };
}
}Performance Considerations
Type-only imports
typescript
// Import only types (removed at runtime)
import type { User } from './types';
import type { ComponentProps } from 'react';
// Re-export types
export type { User, ApiResponse } from './types';Conditional compilation
typescript
// Use const assertions for better inference
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
} as const;
// Type is inferred as literal types, not string | numberBest Practices
- Use strict mode - Enable all strict flags in tsconfig.json
- Prefer interfaces for object shapes - Use types for unions and computed types
- Use const assertions - For better type inference
- Avoid any - Use unknown or proper typing instead
- Use utility types - Leverage built-in utility types
- Document complex types - Add JSDoc comments for complex type definitions
Practice Projects
- Build a type-safe API client with generics
- Create a form validation library with conditional types
- Implement a state management system with branded types
- Design a plugin system with module augmentation