javascript 41 lines · 9 steps

Building a typeahead with an LRU cache

A search-as-you-type helper that caches results, evicts the oldest entries, and deduplicates in-flight requests.

Explained by highlit
1const cache = new Map();
2const inflight = new Map();
3 
4async function fetchResults(query) {
5 const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
6 if (!res.ok) throw new Error(`Search failed: ${res.status}`);
7 return res.json();
8}
9 
10function createTypeahead({ minChars = 2, maxCacheSize = 50 } = {}) {
11 async function search(raw) {
12 const query = raw.trim().toLowerCase();
13 if (query.length < minChars) return [];
14 
15 if (cache.has(query)) {
16 const hit = cache.get(query);
17 cache.delete(query);
18 cache.set(query, hit);
19 return hit;
20 }
21 
22 if (inflight.has(query)) return inflight.get(query);
23 
24 const promise = fetchResults(query)
25 .then((results) => {
26 cache.set(query, results);
27 if (cache.size > maxCacheSize) {
28 cache.delete(cache.keys().next().value);
29 }
30 return results;
31 })
32 .finally(() => inflight.delete(query));
33 
34 inflight.set(query, promise);
35 return promise;
36 }
37 
38 return { search, clear: () => cache.clear() };
39}
40 
41export default createTypeahead;
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A Map preserves insertion order, so deleting and re-setting a key turns it into a simple LRU cache.
  2. 2Tracking in-flight promises lets concurrent identical requests share one network call instead of racing.
  3. 3Normalizing input and enforcing a minimum length keeps the cache small and avoids wasteful queries.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a typeahead with an LRU cache — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code