Evvl

Independent SoTA check: Kimi K3 vs Fable 5 vs GPT-5.6 Sol on a coding task (HN 48999291)

Prompt

Implement a TypeScript LRU cache with per-entry TTL behind a single class: get/set/delete, a configurable max size that evicts least-recently-used entries, lazy expiry on read plus an explicit prune(), and no external dependencies. Include a short usage example. Return complete, runnable code.

Responses with Independent SoTA check: Kimi K3 vs Fable 5 vs GPT-5.6 Sol on a coding task (HN 48999291) (1 rows)

openrouteropenrouter
kimi-k3
moonshotai/kimi-k3
172.07sSlowest by 7x$0.110Priciest by 2x

Here's a complete implementation using a Map (which preserves insertion order) to get O(1) LRU operations with no dependencies:

/**
 * LRU cache with per-entry TTL. No external dependencies.
 *
 * Run directly:   npx ts-node lru-cache.ts
 * Or compile:     npx tsc --target es2020 lru-cache.ts && node lru-cache.js
 */

export interface LRUCacheOptions {
  /** Maximum number of entries. The least-recently-used entry is evicted when exceeded. */
  maxSize: number;
  /** Default TTL in milliseconds for entries set without an explicit ttl. Omit for no expiry. */
  ttl?: number;
}

interface CacheEntry<V> {
  value: V;
  /** Timestamp (ms since epoch) at which the entry expires. Infinity = never. */
  expiresAt: number;
}

export class LRUCache<K, V> {
  private readonly maxSize: number;
  private readonly defaultTtl?: number;

  // Map iteration order = insertion order: least-recently-used first.
  private readonly cache = new Map<K, CacheEntry<V>>();

  constructor(options: LRUCacheOptions) {
    if (!Number.isInteger(options.maxSize) || options.maxSize < 1) {
      throw new Error("maxSize must be a positive integer");
    }
    this.maxSize = options.maxSize;
    this.defaultTtl = options.ttl;
  }

  /** Number of stored entries (may include expired entries not yet pruned/read). */
  get size(): number {
    return this.cache.size;
  }

  /** Returns the value and marks the key as most-recently-used; undefined if missing or expired. */
  get(key: K): V | undefined {
    const entry = this.cache.get(key);
    if (entry === undefined) return undefined;

    if (this.isExpired(entry)) {
      this.cache.delete(key); // lazy expiry on read
      return undefined;
    }

    // Re-insert at the end to mark as most-recently-used.
    this.cache.delete(key);
    this.cache.set(key, entry);
    return entry.value;
  }

  /**
   * Inserts or updates an entry. `ttl` (ms) overrides the default TTL for this
   * entry; pass Infinity for an entry that never expires (ttl <= 0 expires
   * immediately). Returns `this` for chaining.
   */
  set(key: K, value: V, ttl?: number): this {
    const effectiveTtl = ttl ?? this.defaultTtl;
    const expiresAt =
      effectiveTtl === undefined || !Number.isFinite(effectiveTtl)
        ? Infinity
        : Date.now() + effectiveTtl;

    // Delete first so an existing key's position is refreshed.
    this.cache.delete(key);
    this.cache.set(key, { value, expiresAt });

    // Evict least-recently-used entries from the front of the Map.
    while (this.cache.size > this.maxSize) {
      const oldest = this.cache.keys().next();
      if (oldest.done) break; // unreachable, but satisfies the compiler
      this.cache.delete(oldest.value);
    }
    return this;
  }

  /** Removes the entry for `key`; returns true if it existed. */
  delete(key: K): boolean {
    return this.cache.delete(key);
  }

  /** True if a non-expired entry exists (does not affect recency). */
  has(key: K): boolean {
    const entry = this.cache.get(key);
    if (entry === undefined) return false;
    if (this.isExpired(entry)) {
      this.cache.delete(key);
      return false;
    }
    return true;
  }

  /** Removes all expired entries; returns the number removed. */
  prune(): number {
    const now = Date.now();
    let removed = 0;
    for (const [key, entry] of this.cache) {
      if (entry.expiresAt <= now) {
        this.cache.delete(key);
        removed++;
      }
    }
    return removed;
  }

  /** Removes all entries. */
  clear(): void {
    this.cache.clear();
  }

  private isExpired(entry: CacheEntry<V>): boolean {
    return entry.expiresAt <= Date.now();
  }
}

// ---------------------------------------------------------------------------
// Usage example (remove when using LRUCache as a library)
// ---------------------------------------------------------------------------

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function main(): Promise<void> {
  // --- LRU eviction by max size ---
  const lru = new LRUCache<string, number>({ maxSize: 3 });
  lru.set("a", 1).set("b", 2).set("c", 3);
  lru.get("a");              // touch "a" → "b" is now least-recently-used
  lru.set("d", 4);           // exceeds maxSize → evicts "b"
  console.log(lru.get("b")); // undefined (evicted)
  console.log(lru.get("a")); // 1
  console.log(lru.size);     // 3

  // --- Per-entry TTL ---
  const cache = new LRUCache<string, number>({ maxSize: 100, ttl: 1_000 });
  cache.set("session", 42, 100);    // per-entry TTL overrides the default
  cache.set("pinned", 7, Infinity); // never expires
  await sleep(150);
  console.log(cache.get("session")); // undefined — lazily expired on read
  console.log(cache.get("pinned"));  // 7

  // --- Explicit prune (expired entries nobody reads still occupy space) ---
  cache.set("ghost1", 1, 50);
  cache.set("ghost2", 2, 50);
  await sleep(60);
  console.log(cache.size);    // 3 (pinned + two expired ghosts)
  console.log(cache.prune()); // 2 — expired entries removed
  console.log(cache.size);    // 1

  // --- delete / has ---
  cache.set("temp", 9);
  cache.delete("temp");
  console.log(cache.has("temp")); // false
}

main();

Design notes:

  • LRU via Map ordering: JavaScript Maps iterate in insertion order. Every get/set re-inserts the key at the end, so the first key is always the least-recently-used — giving O(1) get/set/evict with no extra bookkeeping.
  • Expiry semantics: an entry expires when Date.now() >= setTime + ttl. Expired entries are removed lazily on get/has, or eagerly via prune() (O(n)) — useful to call on a timer if many entries expire without being read.
  • TTL precedence: per-entry ttl in set() > constructor default ttl > no expiry. Infinity explicitly pins an entry.
  • Eviction policy: when maxSize is exceeded, the strictly least-recently-used entry is evicted, regardless of whether some other entry is expired-but-unread (call prune() first if you prefer otherwise).
openrouteropenrouter
claude-fable-5
anthropic/claude-fable-5
22.24sFastest by 7x$0.107

LRU Cache with Per-Entry TTL

interface CacheEntry<V> {
  value: V;
  /** Absolute expiry timestamp in ms, or Infinity for no TTL. */
  expiresAt: number;
}

interface LRUCacheOptions {
  /** Maximum number of entries before LRU eviction kicks in. */
  maxSize: number;
  /** Default TTL in milliseconds applied when set() gets no explicit TTL. */
  defaultTTL?: number;
}

class LRUCache<K, V> {
  private readonly maxSize: number;
  private readonly defaultTTL: number;
  // Map preserves insertion order; we treat the *last* entry as most recently used.
  private readonly store = new Map<K, CacheEntry<V>>();

  constructor(options: LRUCacheOptions) {
    if (!Number.isInteger(options.maxSize) || options.maxSize <= 0) {
      throw new RangeError("maxSize must be a positive integer");
    }
    this.maxSize = options.maxSize;
    this.defaultTTL = options.defaultTTL ?? Infinity;
  }

  /**
   * Returns the value if present and not expired, otherwise undefined.
   * Expired entries are removed lazily. A hit refreshes LRU recency.
   */
  get(key: K): V | undefined {
    const entry = this.store.get(key);
    if (entry === undefined) return undefined;

    if (this.isExpired(entry)) {
      this.store.delete(key); // lazy expiry
      return undefined;
    }

    // Refresh recency: move to the end of the Map.
    this.store.delete(key);
    this.store.set(key, entry);
    return entry.value;
  }

  /**
   * Inserts or updates a key. Optional ttl (ms) overrides the default.
   * Evicts the least-recently-used entry if capacity is exceeded.
   */
  set(key: K, value: V, ttl?: number): this {
    const effectiveTTL = ttl ?? this.defaultTTL;
    const expiresAt =
      effectiveTTL === Infinity ? Infinity : Date.now() + effectiveTTL;

    // Delete first so re-insertion moves the key to most-recent position.
    this.store.delete(key);
    this.store.set(key, { value, expiresAt });

    if (this.store.size > this.maxSize) {
      // Oldest entry = first key in Map iteration order.
      const oldestKey = this.store.keys().next().value as K;
      this.store.delete(oldestKey);
    }
    return this;
  }

  /** Removes a key. Returns true if it existed. */
  delete(key: K): boolean {
    return this.store.delete(key);
  }

  /** True if the key exists and is not expired (does not refresh recency). */
  has(key: K): boolean {
    const entry = this.store.get(key);
    if (entry === undefined) return false;
    if (this.isExpired(entry)) {
      this.store.delete(key);
      return false;
    }
    return true;
  }

  /** Eagerly removes all expired entries. Returns the number removed. */
  prune(): number {
    const now = Date.now();
    let removed = 0;
    for (const [key, entry] of this.store) {
      if (entry.expiresAt <= now) {
        this.store.delete(key);
        removed++;
      }
    }
    return removed;
  }

  /** Removes everything. */
  clear(): void {
    this.store.clear();
  }

  /** Current entry count (may include not-yet-pruned expired entries). */
  get size(): number {
    return this.store.size;
  }

  private isExpired(entry: CacheEntry<V>): boolean {
    return entry.expiresAt <= Date.now();
  }
}

// ---------------------------------------------------------------------------
// Usage example
// ---------------------------------------------------------------------------

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

async function main() {
  const cache = new LRUCache<string, number>({ maxSize: 3, defaultTTL: 1000 });

  cache.set("a", 1);
  cache.set("b", 2);
  cache.set("c", 3);

  console.log(cache.get("a")); // 1  (also makes "a" most recently used)

  cache.set("d", 4); // capacity exceeded -> evicts "b" (least recently used)
  console.log(cache.get("b")); // undefined (evicted)
  console.log(cache.get("d")); // 4

  cache.set("short", 99, 100); // per-entry TTL of 100ms overrides default
  await sleep(150);
  console.log(cache.get("short")); // undefined (lazily expired on read)

  cache.set("x", 10, 50);
  await sleep(100);
  console.log("size before prune:", cache.size); // may include expired "x"
  console.log("pruned:", cache.prune());          // removes expired entries
  console.log("size after prune:", cache.size);

  cache.delete("a");
  console.log(cache.has("a")); // false
}

main();

Design notes

  • LRU via Map ordering — JavaScript Map iterates in insertion order, so recency is tracked by deleting and re-inserting a key on every get/set hit. The least-recently-used entry is always the first key, making eviction O(1).
  • Lazy expiry — TTLs are stored as absolute timestamps; get and has check expiry on access and delete stale entries without any timers. prune() sweeps everything eagerly when you want deterministic memory reclamation.
  • Per-entry TTLset(key, value, ttl) overrides the constructor's defaultTTL; omitting both means the entry never expires (Infinity).
  • No dependencies — runs with just ts-node/tsc or can be pasted into any TS project (target ES2015+).
openrouteropenrouter
gpt-5.6-sol
openai/gpt-5.6-sol
29.11s$0.053Cheapest by 2x
export class LRUCache<K, V> {
  private readonly entries = new Map<
    K,
    { value: V; expiresAt: number | null }
  >();

  constructor(private readonly maxSize: number) {
    if (!Number.isInteger(maxSize) || maxSize <= 0) {
      throw new RangeError("maxSize must be a positive integer");
    }
  }

  /**
   * Returns a value and marks it as recently used.
   * Expired entries are removed and return undefined.
   */
  get(key: K): V | undefined {
    const entry = this.entries.get(key);

    if (entry === undefined) {
      return undefined;
    }

    if (entry.expiresAt !== null && entry.expiresAt <= Date.now()) {
      this.entries.delete(key);
      return undefined;
    }

    // Map preserves insertion order. Reinsert to mark as most recently used.
    this.entries.delete(key);
    this.entries.set(key, entry);

    return entry.value;
  }

  /**
   * Adds or updates an entry.
   * Omit ttlMs, or use Infinity, for an entry that does not expire.
   */
  set(key: K, value: V, ttlMs?: number): this {
    if (ttlMs !== undefined && Number.isNaN(ttlMs)) {
      throw new RangeError("ttlMs must be a number");
    }

    this.entries.delete(key);

    // A non-positive TTL means the entry is already expired.
    if (ttlMs !== undefined && ttlMs <= 0) {
      return this;
    }

    const expiresAt =
      ttlMs === undefined || ttlMs === Infinity
        ? null
        : Date.now() + ttlMs;

    // Remove stale entries before evicting a live LRU entry.
    if (this.entries.size >= this.maxSize) {
      this.prune();
    }

    while (this.entries.size >= this.maxSize) {
      const oldest = this.entries.keys().next();

      if (oldest.done) {
        break;
      }

      this.entries.delete(oldest.value);
    }

    this.entries.set(key, { value, expiresAt });
    return this;
  }

  delete(key: K): boolean {
    return this.entries.delete(key);
  }

  /**
   * Removes all expired entries and returns the number removed.
   */
  prune(): number {
    const now = Date.now();
    let removed = 0;

    for (const [key, entry] of this.entries) {
      if (entry.expiresAt !== null && entry.expiresAt <= now) {
        this.entries.delete(key);
        removed++;
      }
    }

    return removed;
  }
}

// Usage example
const cache = new LRUCache<string, number>(2);

cache.set("a", 1, 5_000);
cache.set("b", 2);          // No expiry

console.log(cache.get("a")); // 1; "a" is now most recently used

cache.set("c", 3);          // Evicts "b", the least recently used entry

console.log(cache.get("b")); // undefined
console.log(cache.get("c")); // 3

cache.set("short-lived", 4, 10);

setTimeout(() => {
  console.log(cache.get("short-lived")); // undefined; lazily removed
  console.log(cache.prune());            // Removes any other expired entries
}, 20);

Discover if you're paying too much for an AI model that's too slow.

Sign in with OpenRouter