Advanced TypeScript Patterns Every Developer Should Know 🚀
"Make impossible states impossible" - TypeScript's type system at its finest.
Table of Contents
Introduction
TypeScript has revolutionized how we write JavaScript applications. In this comprehensive guide, we'll explore advanced patterns that make TypeScript a powerful tool for building robust applications.
Why Advanced Patterns Matter
- Type Safety
- Catch errors at compile time
- Better IDE support
- Self-documenting code
- Code Reusability
- DRY principles
- Maintainable codebase
Advanced Generic Patterns
1. Conditional Types
Here's an example of a powerful conditional type:
type IsArray<T> = T extends any[] ? true : false
type StringOrNumber<T> = T extends string | number ? T : never
// Usage
type Test1 = IsArray<string[]> // true
type Test2 = IsArray<number> // false
2. Mapped Types with Key Remapping
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
interface Person {
name: string
age: number
}
type PersonGetters = Getters<Person>
// Results in:
// {
// getName: () => string;
// getAge: () => number;
// }
Practical Examples
Let's look at a real-world example of a type-safe event emitter:
interface EventMap {
userLoggedIn: { userId: string; timestamp: number }
userLoggedOut: { timestamp: number }
error: { code: number; message: string }
}
class TypeSafeEventEmitter {
private listeners: Partial<{
[K in keyof EventMap]: ((data: EventMap[K]) => void)[]
}> = {}
on<K extends keyof EventMap>(event: K, callback: (data: EventMap[K]) => void) {
if (!this.listeners[event]) {
this.listeners[event] = []
}
this.listeners[event]?.push(callback)
}
emit<K extends keyof EventMap>(event: K, data: EventMap[K]) {
this.listeners[event]?.forEach(callback => callback(data))
}
}
Performance Considerations
When working with complex types, consider the following metrics:
| Pattern | Compile Time | Runtime Impact | Type Safety |
|---|---|---|---|
| Generics | ⭐⭐⭐ | None | High |
| Unions | ⭐⭐ | None | High |
| Any | ⭐ | None | Low |
Code Quality Metrics
flowchart LR
A([Type Safety]) --> B([Code Quality])
B --> C([Maintainability])
B --> D([Reliability])
C --> E([Developer Experience])
D --> E
Best Practices Checklist
- Use generics for reusable components
- Implement proper error handling
- Document complex type patterns
- Write unit tests for type behavior
Example Implementation
Here's a practical example of implementing a type-safe API client:
interface ApiResponse<T> {
data: T
status: number
timestamp: number
}
interface ApiError {
code: number
message: string
}
async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
try {
const response = await fetch(url)
const data: T = await response.json()
return {
data,
status: response.status,
timestamp: Date.now(),
}
} catch (error) {
throw {
code: 500,
message: error instanceof Error ? error.message : 'Unknown error',
} as ApiError
}
}
Advanced Type Utilities
Here's a visualization of how TypeScript's type system works:
Type Hierarchy
└── any
├── unknown
│ ├── primitive types
│ │ ├── string
│ │ ├── number
│ │ └── boolean
│ └── object types
│ ├── interfaces
│ └── classes
└── never
Keyboard Shortcuts
| Action | Windows/Linux | macOS |
|---|---|---|
| Quick Fix | Ctrl + . | ⌘ + . |
| Go to Definition | F12 | F12 |
| Find References | Shift + F12 | Shift + F12 |
Conclusion
TypeScript's type system is incredibly powerful when used correctly. By understanding and implementing these advanced patterns, you can:
- Write more maintainable code
- Catch errors early
- Improve team collaboration
- Create better developer experiences
💡 Pro Tip: Always start with the simplest type that meets your needs, and only add complexity when necessary.