Blog Build a typeahead search box that feels instant
Build a typeahead search box that feels instant
TL;DR A good typeahead debounces input so it does not query on every keystroke, cancels stale requests so results do not arrive out of order, and supports the keyboard. Get those three right and search feels instant without hammering your server or showing the wrong results.
A search box that suggests results as you type feels great when it is done right and terrible when it is not: laggy, flooding the server, or flashing the wrong results. Three techniques separate the good from the bad. Here they are.
Debounce the input
If you query on every keystroke, you send a request for "j", "ja", "jav", "java", when the user only ever wanted "javascript". That floods your server and wastes work on strings the user is typing through.
Debouncing waits for a short pause before querying:
let timer;
input.addEventListener("input", () => {
clearTimeout(timer);
timer = setTimeout(() => search(input.value), 200);
});
Now you query once the user pauses, for what they actually meant.
Cancel stale requests
Requests do not always return in order. A slow query for an earlier term can land after a fast query for the current one, overwriting the right results with stale ones. The user sees results for what they typed two keystrokes ago.
Cancel or ignore anything but the latest request:
let controller;
async function search(query) {
controller?.abort(); // cancel the previous request
controller = new AbortController();
const res = await fetch(`/search?q=${query}`, { signal: controller.signal });
render(await res.json());
}
Only the current query's results ever reach the screen.
Support the keyboard
Anyone using a typeahead is already typing, so they expect to stay on the keyboard. Arrow keys move through suggestions, Enter selects, Escape dismisses. A typeahead you can only navigate with the mouse feels broken.
Track a highlighted index, move it with the arrow keys, and act on Enter. It is a small amount of state that makes the difference between a real search box and a toy.
Putting it together
Debounce so you search for intent, not keystrokes. Cancel so results never arrive out of order. Handle the keyboard so it feels native to type through. None of these are complicated on their own, and together they turn a naive search box, which lags, floods, and flickers, into one that feels instant and correct. That combination is what people mean when they say search "just works."
FAQ
Why debounce a typeahead?
Without debouncing, you fire a request on every keystroke, which floods the server and wastes work, since the user is still typing. Debouncing waits until they pause briefly, then queries once, so you search for what they meant rather than every intermediate string.
What is the out-of-order results problem?
Requests can return in a different order than they were sent, so a slower query for an earlier term can arrive after a faster one for the current term and overwrite the right results with stale ones. You fix it by cancelling or ignoring responses for anything but the latest query.
Does a typeahead need keyboard support?
Yes. Users expect to move through suggestions with the arrow keys, pick one with Enter, and dismiss with Escape. A typeahead you can only use with the mouse feels broken to anyone typing, which is everyone using it.