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
Testing Like a Pro with Vitest (2025)
Fast, delightful testing for React and TypeScript. Learn how to structure tests, mock networks, and measure coverage with minimal boilerplate.
Web Development Trends 2025: What's Actually Happening
Real web development trends for 2025 - AI integration, edge computing, WebAssembly, new frameworks, and what's actually worth learning.
Bolt.new: The AI That Builds Full-Stack Apps in Your Browser
Deep dive into Bolt.new - the AI that builds, deploys, and hosts full-stack apps. Real examples, limitations, and how it compares to v0 and Cursor.