Caching AI Responses
Skip repeat AI calls by caching answers by prompt and deduplicating requests already in flight.
TL;DR
- Key each answer by its
promptand reuse the result. - Deduplicate requests already
inFlightfor the same key. - Persist a response
cacheacross renders and sessions.
Cache by Prompt Key
Build a KeyNormalize the prompt into a stable key.
const key = prompt.trim().toLowerCase();Check FirstReturn a cached answer before calling.
if (cache.has(key)) return cache.get(key);Store the AnswerSave the result for next time.
cache.set(key, text);Deduplicate In-Flight
Track PromisesCache the pending promise, not just the value.
if (inFlight.has(key))
return inFlight.get(key);Share One CallBoth callers await the same request.
const p = callModel(prompt);
inFlight.set(key, p);Clean UpDrop the promise once it settles.
p.finally(() => inFlight.delete(key));Use React Query
useQueryCache and dedupe with a single hook.
const { data } = useQuery({
queryKey: ['ai', prompt],
queryFn: () => ask(prompt),
});Stable KeyThe queryKey identifies the cached entry.
queryKey: ['ai', prompt]Set FreshnessstaleTime keeps answers cached longer.
staleTime: 60_000Persist Results
Module CacheA Map outside components survives remounts.
const cache = new Map<string, string>();Local StoragePersist stable answers across reloads.
localStorage.setItem(key, text);Server CacheCache in the route to share across users.
// route-level cache = 1 call for allTips
- Reach for
React QueryorSWR; they cache by key, dedupe in-flight requests, and return the cached answer instantly on repeat. - Normalize the
cacheKeyby trimming, lowercasing, and sorting options so equivalent prompts hit one entry instead of missing.
Warnings
- Do not cache personalized or fast-changing answers forever; set a
staleTimeso results refresh when they should. - A cache in a component's
statedies on unmount; use a module store, React Query, or storage to keep it across mounts.
In Practice
An ask helper that returns cached answers and shares in-flight requests for the same prompt.
- A module-level Map holds answers keyed by the normalized prompt.
- A cache hit returns instantly with no network call.
- An in-flight map shares one request among duplicate callers.
- The promise is removed from in-flight once it resolves.
const cache = new Map<string, string>();
const inFlight = new Map<string, Promise<string>>();
async function ask(prompt: string) {
const key = prompt.trim().toLowerCase();
if (cache.has(key)) return cache.get(key)!;
if (inFlight.has(key)) return inFlight.get(key)!;
const p = fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ prompt }),
})
.then((r) => r.json())
.then((d) => {
cache.set(key, d.text);
return d.text as string;
})
.finally(() => inFlight.delete(key));
inFlight.set(key, p);
return p;
}FAQ
Store answers in a cache keyed by the prompt. Before calling the model, check the cache; on a hit, return it instantly. Libraries like React Query and SWR do this for you, including in-flight deduplication and configurable freshness.
If two components ask the same question at once, dedup shares a single request instead of firing two. The cache tracks the inFlight promise by key and hands the same result to both callers, halving cost and load.
For stable answers, yes. A module-level Map, localStorage, or the query client keeps results across component mounts and page reloads, so a returning user gets an instant answer with no new tokens spent.