TypeDrop

2026-08-17 Challenge

2026-08-17 Hard

Typed LRU Cache with TTL, Generics & Branded Keys

You're building the in-process caching layer for a multi-tenant SaaS platform. Every service — user sessions, feature flags, rate-limit counters — needs a cache that evicts the least-recently-used entry when full, respects per-entry TTLs, and enforces that keys are branded strings so callers can't accidentally mix cache namespaces at the type level.

Goals

  • Define `Brand<Base, Tag>` and three concrete branded key types (`SessionKey`, `FeatureFlagKey`, `RateLimitKey`) so the compiler rejects mixing cache namespaces.
  • Implement `LRUCache<K extends string, V>` with O(1) doubly-linked-list eviction, per-entry TTL, and a `CacheResult<V>` discriminated union returned from `get()`.
  • Track hit, miss, expiration, and eviction counters in `CacheStats` and expose them via a `Readonly` snapshot from `stats()`.
  • Implement `createNamespacedCaches()` with a mapped return type that preserves the branded key type and value type inferred from each entry in the options map.
challenge.ts
// Key types & cache result — core of the challenge
type Brand<Base, Tag> = Base & { readonly __tag: Tag };

type SessionKey     = Brand<string, "SessionKey">;
type FeatureFlagKey = Brand<string, "FeatureFlagKey">;

type CacheResult<V> =
  | { status: "hit";     value: V      }
  | { status: "miss"                   }
  | { status: "expired"; key: string   };

interface CacheEntry<V> {
  value:     V;
  expiresAt: number | null;
  prev:      CacheEntry<V> | null;
  next:      CacheEntry<V> | null;
}

// Main class signature — your task is to implement it
class LRUCache<K extends string, V> {
  constructor(options: LRUCacheOptions<K, V>) { /* … */ }
  set(key: K, value: V, opts?: SetOptions): void { /* … */ }
  get(key: K): CacheResult<V>              { /* … */ }
  delete(key: K): boolean                  { /* … */ }
  peek(key: K): V | undefined              { /* … */ }
  stats(): Readonly<CacheStats>            { /* … */ }
  size(): number                           { /* … */ }
}
Hints (click to reveal)

Hints

  • For `Brand<Base, Tag>`, intersect `Base` with `{ readonly __tag: Tag }` — the `__tag` property never exists at runtime but makes each brand structurally distinct to the compiler.
  • For the doubly-linked-list, maintain a sentinel `head` (most-recent) and `tail` (least-recent) node; `moveToHead` and `removeTail` give you O(1) `get` and eviction.
  • For `createNamespacedCaches`, look at how mapped types can index into `M` with `M[NS]` and use helper types to extract the `K` and `V` type parameters from an `LRUCacheOptions<K, V>`.

Or clone locally

git clone -b challenge/2026-08-17 https://github.com/niltonheck/typedrop.git