The 30 second answer
The reported task is a search box that fetches suggestions on every keystroke. The important parts are a complete happy path, tests, clear names, and not calling the backend on every key. I debounce the input so a request goes out only after the user pauses, and I ignore stale responses so a slow earlier query cannot overwrite a later one. A short cache and a minimum query length are the usual next optimizations.
Reported. A 2024 frontend Senior Software Engineer take-home on Codility asked for a React and Node application: a search box with an auto-suggestion list on every keystroke. The write-up says the bar was the required features, unit tests with several scenarios, comments, meaningful names, and a performance habit — debounce on the keystroke handler. The later discussion asked what happens if imports are removed, how to optimize further, and whether you can write your own debounce.
This is a frontend exercise, included because it is a fully described Zalando coding task. On a Java backend interview the same performance idea shows up as "don't call the search API on every key."
Approach.
- Debounce the query. After each keystroke, reset a short timer (for example 200–300 ms, Inferred — the report did not name a delay). Only when the user pauses do you send the request. That cuts traffic and load on the suggest endpoint.
- Ignore stale responses. Keystrokes 1, 2, 3 can be in flight together. If the response for "sa" arrives after the response for "sal", the list would jump backwards. Give every request a monotonic id, or abort the previous fetch, and only publish a result when it still matches the latest query.
- Tests. Typeahead, empty query, no hits, a delayed earlier response, and the debounce itself (rapid keystrokes produce one call).
- Further optimizations, which match the reported follow-up: a minimum query length, an in-memory cache keyed by the normalized string, request cancellation, and pagination of the suggestion list.
A small Java debounce and stale-result guard, compiled and run on OpenJDK 21.0.13, behaves as follows: three tasks submitted 20 ms apart, with an 80 ms delay, run only the last one; after a second request id is issued, the first id is no longer current.
import java.util.concurrent.*;
final class Debouncer implements AutoCloseable {
private final ScheduledExecutorService exec =
Executors.newSingleThreadScheduledExecutor();
private final long delayMs;
private ScheduledFuture<?> pending;
Debouncer(long delayMs) { this.delayMs = delayMs; }
synchronized void submit(Runnable task) {
if (pending != null) pending.cancel(false);
pending = exec.schedule(task, delayMs, TimeUnit.MILLISECONDS);
}
@Override public void close() { exec.shutdownNow(); }
}
final class StaleGuard {
private long latest;
synchronized long nextId() { return ++latest; }
synchronized boolean isCurrent(long id) { return id == latest; }
}Edge cases. Empty and very short queries; the user clearing the box; duplicate keys; a failed request that must not block the next one; accessibility of the suggestion list (keyboard, screen reader) on a frontend implementation.