Memoization and Performance Optimization
Caching function results to improve performance
Challenge
Section titled “Challenge”You have an expensive function (API calls, complex calculations) that’s called repeatedly with the same arguments. Performance is degrading. How do you cache results efficiently?
Explanation
Section titled “Explanation”Memoization caches function results based on arguments. It trades memory for speed, making it valuable for pure functions with deterministic outputs.
Basic Memoization
Section titled “Basic Memoization”// Simple memoizationfunction memoize(fn) { const cache = {};
return function(...args) { const key = JSON.stringify(args); // Simple key generation
if (key in cache) { console.log('Cache hit:', key); return cache[key]; }
console.log('Cache miss:', key); const result = fn(...args); cache[key] = result; return result; };}
// Expensive functionfunction fibonacci(n) { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2);}
const memoFib = memoize(fibonacci);
console.time('With cache');console.log(memoFib(40));console.log(memoFib(40)); // Cache hit!console.timeEnd('With cache');
console.time('Without cache');console.log(fibonacci(40));console.timeEnd('Without cache');Solution: Smart Cache Management
Section titled “Solution: Smart Cache Management”class Cache { constructor(maxSize = 100, ttl = null) { this.maxSize = maxSize; this.ttl = ttl; this.cache = new Map(); this.accessOrder = []; }
set(key, value) { // Evict oldest if at capacity if (this.cache.size >= this.maxSize) { const oldestKey = this.accessOrder.shift(); this.cache.delete(oldestKey); }
// Set with expiration const entry = { value }; if (this.ttl) { entry.expires = Date.now() + this.ttl; }
this.cache.set(key, entry); this.accessOrder.push(key); }
get(key) { const entry = this.cache.get(key); if (!entry) return undefined;
if (entry.expires && Date.now() > entry.expires) { this.cache.delete(key); return undefined; }
return entry.value; }}
function memoizeWithCache(fn, cache = new Cache()) { return function(...args) { const key = JSON.stringify(args);
let result = cache.get(key); if (result === undefined) { result = fn(...args); cache.set(key, result); }
return result; };}
// Usageconst cache = new Cache(10, 60000); // 10 items, 1 minute TTLconst cachedFetch = memoizeWithCache(fetch, cache);Practical Example: API Memoization
Section titled “Practical Example: API Memoization”function createMemoizedFetch(maxAge = 5 * 60 * 1000) { const cache = new Map();
return async function(url, options = {}) { const cacheKey = url + JSON.stringify(options);
// Check cache const cached = cache.get(cacheKey); if (cached && Date.now() - cached.timestamp < maxAge) { return cached.data; }
// Fetch if not in cache or expired const response = await fetch(url, options); const data = await response.json();
// Store in cache cache.set(cacheKey, { data, timestamp: Date.now() });
return data; };}
const fetchUser = createMemoizedFetch();
// First call: hits APIconst user1 = await fetchUser('/api/user/1');
// Second call: uses cache (within 5 minutes)const user2 = await fetchUser('/api/user/1');
// After 5 minutes: hits API againsetTimeout(async () => { const user3 = await fetchUser('/api/user/1');}, 6 * 60 * 1000);Decorator Pattern for Memoization
Section titled “Decorator Pattern for Memoization”// Memoization decoratorfunction memoized(target, propertyKey, descriptor) { const originalMethod = descriptor.value; const cache = new Map();
descriptor.value = function(...args) { const key = JSON.stringify(args);
if (cache.has(key)) { return cache.get(key); }
const result = originalMethod.apply(this, args); cache.set(key, result); return result; };
return descriptor;}
// Usage with classes (requires decorators)class Calculator { @memoized expensiveComputation(n) { console.log(`Computing for ${n}`); return n * n; }}Performance Comparison
Section titled “Performance Comparison”// Compare performancefunction benchmark(fn, iterations = 1000) { const start = performance.now();
for (let i = 0; i < iterations; i++) { fn(Math.floor(Math.random() * 100)); }
return performance.now() - start;}
function slow(n) { let sum = 0; for (let i = 0; i < 1000000; i++) { sum += Math.sqrt(i + n); } return sum;}
const memoSlow = memoize(slow);
const timeWithoutCache = benchmark(slow, 100);const timeWithCache = benchmark(memoSlow, 100);
console.log(`Without cache: ${timeWithoutCache}ms`);console.log(`With cache: ${timeWithCache}ms`);console.log(`Speedup: ${(timeWithoutCache / timeWithCache).toFixed(2)}x`);