Skip to content
Plurafy

Memoization and Performance Optimization

Caching function results to improve performance

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?

Memoization caches function results based on arguments. It trades memory for speed, making it valuable for pure functions with deterministic outputs.

// Simple memoization
function 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 function
function 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');
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;
};
}
// Usage
const cache = new Cache(10, 60000); // 10 items, 1 minute TTL
const cachedFetch = memoizeWithCache(fetch, cache);
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 API
const 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 again
setTimeout(async () => {
const user3 = await fetchUser('/api/user/1');
}, 6 * 60 * 1000);
// Memoization decorator
function 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;
}
}
// Compare performance
function 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`);