typescriptintermediate

In-Memory Caching with LRU Strategy

Implement LRU cache with TTL expiration, size limits, and cache-aside pattern for Node.js applications.

typescript
interface CacheEntry<T> {
  value: T;
  expiresAt: number;
}

class LRUCache<T> {
  private cache = new Map<string, CacheEntry<T>>();

  constructor(
    private maxSize: number = 1000,
    private defaultTTL: number = 60_000,
  ) {}

  get(key: string): T | null {
    const entry = this.cache.get(key);
    if (!entry) return null;
    if (Date.now() > entry.expiresAt) {
      this.cache.delete(key);
      return null;
    }
    // Move to end (most recently used)
    this.cache.delete(key);
    this.cache.set(key, entry);
    return entry.value;
  }

  set(key: string, value: T, ttl?: number): void {
    if (this.cache.has(key)) this.cache.delete(key);
    if (this.cache.size >= this.maxSize) {
      // Evict least recently used (first key)
      const firstKey = this.cache.keys().next().value;
      if (firstKey !== undefined) this.cache.delete(firstKey);
    }
    this.cache.set(key, {
      value,
      expiresAt: Date.now() + (ttl ?? this.defaultTTL),
    });
  }

  delete(key: string): boolean { return this.cache.delete(key); }
  clear(): void { this.cache.clear(); }
  get size(): number { return this.cache.size; }

  // Cache-aside pattern
  async getOrSet(key: string, fetcher: () => Promise<T>, ttl?: number): Promise<T> {
    const cached = this.get(key);
    if (cached !== null) return cached;
    const value = await fetcher();
    this.set(key, value, ttl);
    return value;
  }
}

// Usage
const cache = new LRUCache<any>(100, 30_000);

async function getUser(id: string) {
  return cache.getOrSet(`user:${id}`, async () => {
    console.log(`  Cache miss: fetching user ${id}`);
    await new Promise(r => setTimeout(r, 100));
    return { id, name: `User ${id}`, ts: Date.now() };
  });
}

async function main() {
  console.log(await getUser('1'));  // miss
  console.log(await getUser('1'));  // hit
  console.log(await getUser('2'));  // miss
  console.log(`Cache size: ${cache.size}`);
}

main();

Use Cases

  • API response caching
  • Database query result caching
  • Expensive computation memoization

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.