typescript
39 lines · 8 steps
A TTL cache keyed by object identity
A generic cache uses a WeakMap so object keys are garbage-collected automatically while entries expire on a time-to-live.
Explained by
highlit
1type CacheEntry<V> = {
2 value: V;
3 computedAt: number;
4};
5
6export class ObjectCache<K extends object, V> {
7 private readonly store = new WeakMap<K, CacheEntry<V>>();
8
9 constructor(private readonly ttlMs: number = Infinity) {}
10
11 get(key: K): V | undefined {
12 const entry = this.store.get(key);
13 if (!entry) return undefined;
14 if (Date.now() - entry.computedAt > this.ttlMs) {
15 this.store.delete(key);
16 return undefined;
17 }
18 return entry.value;
19 }
20
21 set(key: K, value: V): V {
22 this.store.set(key, { value, computedAt: Date.now() });
23 return value;
24 }
25
26 has(key: K): boolean {
27 return this.get(key) !== undefined;
28 }
29
30 getOrCompute(key: K, factory: (key: K) => V): V {
31 const cached = this.get(key);
32 if (cached !== undefined) return cached;
33 return this.set(key, factory(key));
34 }
35
36 delete(key: K): boolean {
37 return this.store.delete(key);
38 }
39}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A WeakMap lets object keys be reclaimed by GC, so the cache never leaks memory holding dead keys alive.
- 2Storing a timestamp with each entry turns a plain map into a TTL cache checked lazily on read.
- 3getOrCompute composes get and set into a single memoization primitive callers can build on.
Related explainers
typescript
import { registerLocaleData } from '@angular/common'; import localeFr from '@angular/common/locales/fr'; import localeFrExtra from '@angular/common/locales/extra/fr'; import localeDe from '@angular/common/locales/de';
Locale-aware bootstrapping in Angular
i18n
localization
dependency-injection
Intermediate
8 steps
typescript
import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import * as Joi from 'joi';
Validating env config at boot in NestJS
configuration
schema-validation
environment-variables
Intermediate
8 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { Cache } from 'cache-manager'; import { InjectRepository } from '@nestjs/typeorm';
A cache-aside country lookup in NestJS
cache-aside
dependency-injection
batch-lookup
Intermediate
8 steps
typescript
import { Injectable, effect, signal, computed } from '@angular/core'; interface Preferences { theme: 'light' | 'dark';
A signal-based preferences store in Angular
signals
state-management
persistence
Intermediate
7 steps
javascript
import { useState, useEffect, useCallback, useRef } from 'react'; const cache = new Map(); const inflight = new Map();
Building a stale-while-revalidate hook in React
caching
request-deduplication
custom-hooks
Advanced
10 steps
php
<?php namespace App\Services;
Building a cached daily leaderboard in Laravel
caching
aggregation
eager-loading
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/a-ttl-cache-keyed-by-object-identity-explained-typescript-18b5/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.