TypeDrop
2026-07-25 Challenge
2026-07-25
Medium
Typed LRU Cache with TTL & Typed Eviction Callbacks
You're building the in-process caching layer for a high-traffic API server. Cached entries must expire after a configurable TTL, the cache must evict the least-recently-used entry when it reaches capacity, and callers need strongly-typed eviction callbacks so downstream systems can react (e.g. flush to disk, emit metrics) without losing the shape of the evicted value.
Goals
- Define a three-variant discriminated union `EvictionReason` and wire it into a generic `EvictionCallback<K,V>` type.
- Implement `createLRUCache<K,V>` backed by a `Map`, using the delete-and-reinsert trick to maintain LRU order without a doubly-linked list.
- Correctly handle TTL expiry in `get()` and `has()` — firing `onEvict` with the `ttl` variant only on an actual cache read, not on a peek.
- Return entries in MRU-first order from `keys()`, filtering out any stale (expired) entries.
challenge.ts
// Key types and main factory signature — enough to understand the challenge
export type EvictionReason =
| { kind: "capacity" }
| { kind: "ttl"; expiredAt: number }
| { kind: "manual" };
export type EvictionCallback<K, V> = (
key: K,
value: V,
reason: EvictionReason
) => void;
export type LRUCacheOptions<K, V> = {
capacity: number;
ttlMs?: number;
onEvict?: EvictionCallback<K, V>;
};
export interface ILRUCache<K, V> {
set(key: K, value: V): void;
get(key: K): V | undefined;
has(key: K): boolean;
delete(key: K): boolean;
clear(): void;
readonly size: number;
keys(): K[];
}
// TODO: implement this 👇
export function createLRUCache<K, V>(
options: LRUCacheOptions<K, V>
): ILRUCache<K, V> {
throw new Error("Not implemented");
}
Hints (click to reveal)
Hints
- A plain `Map<K, CacheEntry<V>>` preserves insertion order — deleting a key and re-inserting it on every access is all you need to implement LRU without any extra data structure.
- For the `EvictionReason` discriminated union, the `kind` property is your type-narrowing tag — make sure every call site constructs the exact variant object (e.g. `{ kind: 'ttl', expiredAt: Date.now() }`).
- `has()` must NOT call `get()` internally — it should peek without updating `lastUsedAt`, so implement it by reading the Map directly and checking the TTL manually.
Useful resources
Or clone locally
git clone -b challenge/2026-07-25 https://github.com/niltonheck/typedrop.git