TypeDrop

2026-03-01 Challenge

2026-03-01 Hard

Typed Distributed Cache with TTL & Eviction Policies

You're building the caching layer for a high-throughput microservice platform. Each cache namespace has its own value shape, TTL strategy, and eviction policy — and the orchestrator must coordinate reads, writes, and invalidations across multiple namespaces with full compile-time safety on every key-value pair.

Goals

  • Define the `EvictionPolicy` discriminated union and `NamespaceConfig<V>` generic with optional serialize/deserialize pair.
  • Implement `NamespaceConfigs<S>` as a mapped type so each namespace key binds to the correctly-typed config.
  • Implement `createCacheOrchestrator` with per-namespace LRU eviction, lazy TTL expiry, accurate stats counters, and full `CacheOrchestrator<S>` interface compliance.
  • Implement the `cacheKey` branded-type helper with runtime length validation.
challenge.ts
// Key types at a glance

type AppSchema = {
  users: { id: number; name: string };
  tokens: string;
  counters: number;
};

export type EvictionPolicy =
  | { kind: "lru"; maxSize: number }
  | { kind: "ttl"; defaultTtlMs: number }
  | { kind: "none" };

export type NamespaceConfig<V> = {
  policy: EvictionPolicy;
  serialize?:   (value: V) => string;
  deserialize?: (raw: string) => V;
};

export type NamespaceConfigs<S extends CacheSchema> = {
  [K in keyof S]: NamespaceConfig<S[K]>;
};

export interface CacheOrchestrator<S extends CacheSchema> {
  set<K extends keyof S>(ns: K, key: string, value: S[K], ttlMs?: number): void;
  get<K extends keyof S>(ns: K, key: string): S[K] | undefined;
  delete<K extends keyof S>(ns: K, key: string): boolean;
  invalidateNamespace<K extends keyof S>(ns: K): void;
  stats<K extends keyof S>(ns: K): NamespaceStats;
}

export function createCacheOrchestrator<S extends CacheSchema>(
  configs: NamespaceConfigs<S>
): CacheOrchestrator<S> { /* TODO */ throw new Error("Not implemented"); }
Hints (click to reveal)

Hints

  • A `Map<string, Entry<V>>` preserves insertion order — delete then re-insert a key on every access to cheaply maintain LRU order without a doubly-linked list.
  • Narrow the eviction policy with a `switch (policy.kind)` inside `set` and `get` so TypeScript's exhaustiveness checking guides you through each branch.
  • The `NamespaceConfigs<S>` mapped type only needs one line: `[K in keyof S]: NamespaceConfig<S[K]>` — the hard part is making sure the orchestrator's internal store is also keyed generically over `keyof S`.

Or clone locally

git clone -b challenge/2026-03-01 https://github.com/niltonheck/typedrop.git