Advanced TypeScript Patterns Every Developer Should Master
Discover powerful TypeScript patterns including generics, conditional types, mapped types, and template literal types to write better, safer code.
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
Frontend engineer with 4+ years building scalable SaaS products, design systems, CRM, analytics and omnichannel platforms.
More about me →