TypeDrop

2026-09-28 Challenge

2026-09-28 Hard

Typed LRU Cache with TTL & Typed Eviction Policy

You're building the in-memory caching layer for a high-traffic API gateway. Each cache can hold at most N entries; when full it evicts the Least-Recently-Used entry. Entries also carry an optional time-to-live (TTL), and the cache must report *why* a value was evicted through a strongly-typed eviction event system — all without a single `any` or unsafe cast.

Goals

  • Define all five type stubs (EvictionReason, CacheEntry, LRUCacheOptions, SetOptions, CacheStats, CacheResult) with correct generic parameters and discriminated-union tags.
  • Implement the doubly-linked list mechanics (_detach, _insertAfterHead, _evict) so the LRU invariant is maintained on every set() and get().
  • Handle TTL expiry lazily in get(), lookup(), and has() — firing the correct EvictionReason<K> each time.
  • Make stats() return accurate hit/miss/eviction counts and ensure clear() resets all state without firing eviction callbacks.
challenge.ts
// Key types you must define and implement:

type EvictionReason<K> =
  | { reason: "capacity"; evictedKey: K }
  | { reason: "ttl";      expiredAt: number }
  | { reason: "manual" };

type CacheEntry<K, V> = {
  key: K; value: V; expiresAt: number | null;
  prev: CacheEntry<K, V> | null;
  next: CacheEntry<K, V> | null;
};

type LRUCacheOptions<K, V> = {
  capacity: number;
  defaultTTL?: number;
  onEvict?: (key: K, value: V, reason: EvictionReason<K>) => void;
};

type CacheResult<V> = { found: true; value: V } | { found: false };

// Main class signature:
class LRUCache<K, V> {
  constructor(options: LRUCacheOptions<K, V>);
  set(key: K, value: V, options?: SetOptions): void;
  get(key: K): V | undefined;
  lookup(key: K): CacheResult<V>;
  delete(key: K): boolean;
  has(key: K): boolean;
  stats(): CacheStats;
  clear(): void;
}
Hints (click to reveal)

Hints

  • Model CacheEntry<K,V> as a plain object type with prev/next referencing itself — TypeScript handles self-referential object types directly (no interface trick needed).
  • Sentinel nodes (head & tail) never hold real data; the only allowed cast in the whole file is in the constructor where you initialise them with dummy key/value via `as unknown as K`.
  • For lookup(), return the CacheResult union directly from the same internal logic as get() — extract a shared private helper so you don't duplicate TTL/eviction code.

Or clone locally

git clone -b challenge/2026-09-28 https://github.com/niltonheck/typedrop.git