Performance optimization is crucial for building applications that scale. Let's explore advanced techniques for optimizing code at various levels.
Algorithm Optimization
Choosing the right algorithm is the foundation of performance.
typescript
// ❌ O(n²) - Nested loops
function findDuplicates(arr: number[]): number[] {
const duplicates: number[] = [];
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j] && !duplicates.includes(arr[i])) {
duplicates.push(arr[i]);
}
}
}
return duplicates;
}
// ✅ O(n) - Using Set
function findDuplicatesOptimized(arr: number[]): number[] {
const seen = new Set<number>();
const duplicates = new Set<number>();
for (const num of arr) {
if (seen.has(num)) {
duplicates.add(num);
} else {
seen.add(num);
}
}
return Array.from(duplicates);
}Memory Optimization
Reduce memory footprint and prevent memory leaks.
typescript
// Memory pooling for frequently created objects
class ObjectPool<T> {
private pool: T[] = [];
constructor(
private factory: () => T,
private reset: (obj: T) => void,
private initialSize: number = 10
) {
for (let i = 0; i < initialSize; i++) {
this.pool.push(factory());
}
}
acquire(): T {
return this.pool.pop() || this.factory();
}
release(obj: T) {
this.reset(obj);
this.pool.push(obj);
}
}
// Usage for particle systems
const particlePool = new ObjectPool(
() => ({ x: 0, y: 0, vx: 0, vy: 0, life: 0 }),
(p) => { p.x = 0; p.y = 0; p.vx = 0; p.vy = 0; p.life = 0; },
1000
);Database Query Optimization
Optimize database queries for better performance.
typescript
// ❌ N+1 query problem
async function getUsersWithPosts() {
const users = await db.users.findMany();
for (const user of users) {
user.posts = await db.posts.findMany({
where: { userId: user.id }
});
}
return users;
}
// ✅ Optimized with eager loading
async function getUsersWithPostsOptimized() {
return await db.users.findMany({
include: {
posts: true
}
});
}
// ✅ Even better with pagination
async function getUsersWithPostsPaginated(page: number, limit: number) {
const [users, total] = await Promise.all([
db.users.findMany({
skip: (page - 1) * limit,
take: limit,
include: {
posts: {
take: 10,
orderBy: { createdAt: 'desc' }
}
}
}),
db.users.count()
]);
return { users, total, page, pages: Math.ceil(total / limit) };
}Caching Strategies
Implement multi-level caching for maximum performance.
typescript
class MultiLevelCache {
private memoryCache: Map<string, { value: any; expiry: number }>;
constructor(
private redis: Redis,
private db: Database
) {
this.memoryCache = new Map();
}
async get(key: string): Promise<any> {
// Level 1: Memory cache
const memValue = this.memoryCache.get(key);
if (memValue && memValue.expiry > Date.now()) {
return memValue.value;
}
// Level 2: Redis cache
const redisValue = await this.redis.get(key);
if (redisValue) {
this.memoryCache.set(key, {
value: JSON.parse(redisValue),
expiry: Date.now() + 60000 // 1 minute
});
return JSON.parse(redisValue);
}
// Level 3: Database
const dbValue = await this.db.get(key);
// Cache in all levels
await this.redis.setex(key, 3600, JSON.stringify(dbValue));
this.memoryCache.set(key, {
value: dbValue,
expiry: Date.now() + 60000
});
return dbValue;
}
}Lazy Loading and Code Splitting
Load resources only when needed.
typescript
// Lazy load heavy modules
const heavyModule = lazy(() => import('./HeavyModule'));
// Dynamic imports for code splitting
async function processLargeFile(file: File) {
const { processFile } = await import('./file-processor');
return processFile(file);
}
// Intersection Observer for lazy loading images
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target as HTMLImageElement;
img.src = img.dataset.src!;
imageObserver.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => {
imageObserver.observe(img);
});Performance Monitoring
Always measure before optimizing.
typescript
class PerformanceMonitor {
private metrics: Map<string, number[]> = new Map();
measure<T>(name: string, fn: () => T): T {
const start = performance.now();
const result = fn();
const duration = performance.now() - start;
this.record(name, duration);
return result;
}
async measureAsync<T>(name: string, fn: () => Promise<T>): Promise<T> {
const start = performance.now();
const result = await fn();
const duration = performance.now() - start;
this.record(name, duration);
return result;
}
private record(name: string, duration: number) {
if (!this.metrics.has(name)) {
this.metrics.set(name, []);
}
this.metrics.get(name)!.push(duration);
}
getStats(name: string) {
const measurements = this.metrics.get(name) || [];
if (measurements.length === 0) return null;
const sorted = [...measurements].sort((a, b) => a - b);
return {
count: measurements.length,
min: sorted[0],
max: sorted[sorted.length - 1],
avg: measurements.reduce((a, b) => a + b, 0) / measurements.length,
median: sorted[Math.floor(sorted.length / 2)],
p95: sorted[Math.floor(sorted.length * 0.95)],
p99: sorted[Math.floor(sorted.length * 0.99)]
};
}
}Conclusion
Optimization is an iterative process. Profile first, optimize the bottlenecks, and measure the impact. Remember that premature optimization is the root of all evil—always base your optimizations on real data.