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