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.
Useful resources
Or clone locally
git clone -b challenge/2026-09-28 https://github.com/niltonheck/typedrop.git