Advanced TypeScript Patterns Every Developer Should Master
Advanced TypeScript Patterns Every Developer Should Master
TypeScript's type system is incredibly powerful, offering features that go far beyond basic type annotations. Let's explore advanced patterns that will make your code more robust and maintainable.
1. Advanced Generics Patterns
Generic Constraints
Use constraints to limit what types can be used with your generics:
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("Hello"); // ✅ Works
logLength([1, 2, 3]); // ✅ Works
logLength({ length: 5 }); // ✅ Works
// logLength(123); // ❌ Error: number doesn't have length
Multiple Type Parameters
function merge<T, U>(first: T, second: U): T & U {
return { ...first, ...second };
}
const result = merge(
{ name: "John" },
{ age: 30, email: "john@example.com" }
);
// result type: { name: string } & { age: number; email: string }
Generic Utility Functions
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const result = {} as Pick<T, K>;
keys.forEach(key => {
result[key] = obj[key];
});
return result;
}
const user = { id: 1, name: "John", email: "john@example.com", age: 30 };
const userSummary = pick(user, ["name", "email"]);
// Type: { name: string; email: string }
2. Conditional Types
Basic Conditional Types
type IsArray<T> = T extends any[] ? true : false;
type Test1 = IsArray<string[]>; // true
type Test2 = IsArray<number>; // false
Advanced Conditional Types
type Flatten<T> = T extends (infer U)[] ? U : T;
type StringArray = Flatten<string[]>; // string
type NumberType = Flatten<number>; // number
// Extract function return type
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getData(): Promise<string> {
return Promise.resolve("data");
}
type DataType = ReturnType<typeof getData>; // Promise<string>
3. Mapped Types
Basic Mapped Types
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Required<T> = {
[P in keyof T]-?: T[P];
};
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
Advanced Mapped Types
// Create a type where all properties are functions
type Functionize<T> = {
[K in keyof T]: () => T[K];
};
interface User {
name: string;
age: number;
}
type UserFunctions = Functionize<User>;
// {
// name: () => string;
// age: () => number;
// }
// Create getters and setters
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type Setters<T> = {
[K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};
type UserGettersSetters = Getters<User> & Setters<User>;
// {
// getName: () => string;
// getAge: () => number;
// setName: (value: string) => void;
// setAge: (value: number) => void;
// }
4. Template Literal Types
Basic Template Literals
type World = "world";
type Greeting = `hello ${World}`; // "hello world"
// Create event names
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
type HoverEvent = EventName<"hover">; // "onHover"
Advanced Template Literal Patterns
// CSS-in-JS type-safe styling
type CSSValue = string | number;
type CSSProperties = {
color?: CSSValue;
backgroundColor?: CSSValue;
fontSize?: CSSValue;
margin?: CSSValue;
padding?: CSSValue;
};
type CSSPropertyKeys = keyof CSSProperties;
type CSSCustomProperties<T extends string> = {
[K in T as `--${K}`]: CSSValue;
};
type ThemeVariables = CSSCustomProperties<"primary" | "secondary" | "accent">;
// {
// "--primary": CSSValue;
// "--secondary": CSSValue;
// "--accent": CSSValue;
// }
5. Utility Types and Patterns
Deep Partial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
interface Config {
api: {
baseUrl: string;
timeout: number;
};
features: {
analytics: boolean;
notifications: boolean;
};
}
type PartialConfig = DeepPartial<Config>;
// All properties and nested properties become optional
Type-Safe Event Emitter
interface EventMap {
'user:login': { userId: string; timestamp: Date };
'user:logout': { userId: string };
'data:received': { data: any[]; count: number };
}
class TypedEventEmitter<T extends Record<string, any>> {
private listeners: {
[K in keyof T]?: ((data: T[K]) => void)[];
} = {};
on<K extends keyof T>(event: K, listener: (data: T[K]) => void): void {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event]!.push(listener);
}
emit<K extends keyof T>(event: K, data: T[K]): void {
const eventListeners = this.listeners[event];
if (eventListeners) {
eventListeners.forEach(listener => listener(data));
}
}
}
const emitter = new TypedEventEmitter<EventMap>();
// ✅ Type-safe event handling
emitter.on('user:login', ({ userId, timestamp }) => {
console.log(`User ${userId} logged in at ${timestamp}`);
});
// ❌ TypeScript error: Property 'invalidProp' doesn't exist
// emitter.on('user:login', ({ invalidProp }) => {});
6. Brand Types for Type Safety
// Create branded types for better type safety
type UserId = string & { readonly __brand: unique symbol };
type Email = string & { readonly __brand: unique symbol };
function createUserId(id: string): UserId {
return id as UserId;
}
function createEmail(email: string): Email {
if (!email.includes('@')) {
throw new Error('Invalid email');
}
return email as Email;
}
function sendWelcomeEmail(userId: UserId, email: Email): void {
// Implementation
}
const userId = createUserId("123");
const email = createEmail("user@example.com");
sendWelcomeEmail(userId, email); // ✅ Works
// sendWelcomeEmail(email, userId); // ❌ TypeScript error
7. Advanced Function Overloads
interface CreateElementOptions {
className?: string;
id?: string;
}
// Function overloads for different return types
function createElement(tag: 'div'): HTMLDivElement;
function createElement(tag: 'span'): HTMLSpanElement;
function createElement(tag: 'input'): HTMLInputElement;
function createElement(tag: string): HTMLElement;
function createElement(tag: string, options?: CreateElementOptions): HTMLElement {
const element = document.createElement(tag);
if (options?.className) {
element.className = options.className;
}
if (options?.id) {
element.id = options.id;
}
return element;
}
const div = createElement('div'); // Type: HTMLDivElement
const span = createElement('span'); // Type: HTMLSpanElement
const input = createElement('input'); // Type: HTMLInputElement
8. Recursive Types
// File system structure
interface FileSystemNode {
name: string;
type: 'file' | 'directory';
children?: FileSystemNode[];
}
// JSON-like recursive type
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
// Type-safe path navigation
type PathToProperty<T, Path extends string> =
Path extends `${infer Key}.${infer Rest}`
? Key extends keyof T
? PathToProperty<T[Key], Rest>
: never
: Path extends keyof T
? T[Path]
: never;
interface NestedObject {
user: {
profile: {
name: string;
age: number;
};
};
}
type UserName = PathToProperty<NestedObject, 'user.profile.name'>; // string
Best Practices
1. Use Type Guards
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function processValue(value: unknown) {
if (isString(value)) {
// TypeScript knows value is string here
console.log(value.toUpperCase());
}
}
2. Leverage const assertions
const fruits = ['apple', 'banana', 'orange'] as const;
type Fruit = typeof fruits[number]; // 'apple' | 'banana' | 'orange'
const theme = {
colors: {
primary: '#007bff',
secondary: '#6c757d'
}
} as const;
type ThemeColor = typeof theme.colors[keyof typeof theme.colors];
// '#007bff' | '#6c757d'
Conclusion
These advanced TypeScript patterns provide:
- Better type safety through precise type definitions
- Enhanced developer experience with better autocomplete
- Reduced runtime errors through compile-time checks
- More maintainable code with clear type contracts
Master these patterns to write more robust and maintainable TypeScript applications. The investment in learning advanced types pays dividends in code quality and developer productivity.
Remember: The goal isn't to use these patterns everywhere, but to know when they provide value and how to implement them effectively.
About Ansh Gupta
Frontend Developer with 3 years of experience building modern web applications. Based in Indore, India, passionate about React, TypeScript, and creating exceptional user experiences.
Learn more about meRelated Articles
React 19: Revolutionary Features That Change Everything
Explore React 19's groundbreaking features including React Compiler, Actions, Server Components improvements, and new hooks that will transform how we build React apps.
Mastering React Performance: Tips and Tricks for 2024
Learn advanced techniques to optimize your React applications for maximum performance and better user experience. Complete guide with practical examples.