TypeDrop

2026-08-24 Challenge

2026-08-24 Easy

Typed LRU Cache with Generic Constraints & Branded Keys

You're building a client-side cache for a recipe discovery app. Frequently-fetched recipes, user profiles, and search results all need to be memoized with an eviction policy — and each cache instance must be strongly typed to its value shape and use branded string keys to prevent accidental cross-cache lookups.

Goals

  • Define a `Brand<K,T>` phantom type and a `CacheKey<T>` branded string so keys from different cache namespaces are mutually incompatible at compile time.
  • Implement `LRUCache<TKey, TValue>` with correct generic constraints, using a `Map`'s insertion-order guarantee to achieve O(1) LRU eviction.
  • Implement `makeCacheKey` as the single safe boundary between plain strings and branded keys, and use it inside `memoize` so callers never touch branded types directly.
  • Verify that the type system rejects cross-namespace key usage (e.g. passing a `CacheKey<'profile'>` to a `LRUCache<'recipe', …>`) without any runtime overhead.
challenge.ts
// Key types you must define & use:

type Brand<K, T> = K & { readonly __brand: T };
type CacheKey<T extends string> = Brand<string, T>;

// Factory — the ONE permitted type assertion:
function makeCacheKey<T extends string>(namespace: T, raw: string): CacheKey<T>;

// Generic LRU cache class:
class LRUCache<TKey extends string, TValue> {
  constructor(capacity: number);
  get(key: CacheKey<TKey>): TValue | undefined;
  set(key: CacheKey<TKey>, value: TValue): void;
  delete(key: CacheKey<TKey>): boolean;
  get size(): number;
  clear(): void;
}

// Memoize wrapper:
function memoize<TKey extends string, TValue>(
  cache: LRUCache<TKey, TValue>,
  namespace: TKey,
  fn: (raw: string) => TValue
): (raw: string) => TValue;
Hints (click to reveal)

Hints

  • A `Map` preserves insertion order — deleting a key and re-inserting it moves it to the 'end', so `map.keys().next()` always gives you the least-recently-used entry.
  • Branded types are just intersections: `type Brand<K, T> = K & { readonly __brand: T }`. The `__brand` property only exists in the type system, never at runtime.
  • In `memoize`, call `makeCacheKey(namespace, raw)` internally so the returned `(raw: string) => TValue` function has a clean, unbranded public API.

Or clone locally

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