Design a Reusable React Autocomplete Input: API, Portals, Debounce and Caching
AirwallexFrontend Engineer · Technical ScreenLast updated
Design a reusable autocomplete search input for a React application on top of an existing search API. The discussion covers the API contract with pagination and limits, the component and portal architecture, throttling versus debouncing, client-side result caching strategies, and the public props and exports.
Design an autocomplete search input for a web application: a text field that shows matching suggestions in a popup as the user types and lets the user pick one. Build it as a reusable React component. Assume the backend search API already exists.
The interviewer steered the discussion through the topics in the parts below: the backend API contract, the component architecture, throttling versus debouncing, local caching of results, and the component's public interface.
Clarifying Questions
- What is being searched, and does each suggestion need rich rendering such as icons, secondary text or highlighted matches?
- Is selection single or multiple, and may the user submit free text that matches no suggestion?
- Roughly how many results can a query match, and how fast is the existing API?
- Will the component be used inside dialogs, scrolling panels or other popups?
- Which browsers and devices must be supported, and what accessibility standard applies?
Part 1 — Backend API contract
The search API exists, but define the contract the component relies on: request parameters, response shape, pagination and the result limit.
What This Part Should Cover
- Request parameters and a minimal response shape
- The pagination model, and how the limit is chosen and capped
- Out-of-order responses, errors and rate limiting
Part 2 — Component architecture
Break the component into an input, a label, a field wrapper, a popup, a portal and a portal manager. Explain each piece's responsibility, where the state lives, how the pieces communicate, and why the popup is rendered through a portal.
What This Part Should Cover
- Responsibilities of each piece and where the state lives
- Portal rendering, positioning, and the portal manager's role in stacking and dismissal
- Keyboard navigation and screen-reader semantics for the combined input and list
Part 3 — Throttling versus debouncing
Requests should not fire on every keystroke. Compare throttling and debouncing for this component, choose one, and explain how it fits with the rest of the request handling.
What This Part Should Cover
- Definitions of both techniques and their request timelines
- Request volume versus how quickly results appear
- How the choice combines with request cancellation, a minimum query length and cache hits
Part 4 — Local result caching
Cache results on the client. Compare three strategies: an LRU cache of recent queries, reusing the results of a cached query that is close to the current one by string distance, and showing popular results. When does each help, what can go wrong, and how would you combine them?
What This Part Should Cover
- An LRU cache keyed by the normalized query: capacity, eviction and expiry
- Approximate reuse by prefix or string distance, and its limits
- Popular results for empty or short input and as a fallback, and how the three strategies combine
Part 5 — React props and external interface
Define the component's props and external interface. Which components do you export to consuming teams, and which stay internal?
What This Part Should Cover
- Props: data source, controlled and uncontrolled state, callbacks and rendering customization
- Exported versus internal components and hooks
- Stability, accessible defaults and testability of the interface
Hints
Hint 1
Pages that shift: Think about what happens to offset-based pages if the ranking changes between the first and the second request for the same query.
Hint 2
Responses out of order: Responses for earlier keystrokes can arrive after later ones. Decide what the request or the response must carry so the client can tell which input a response belongs to.
Hint 3
Escaping the overflow: Picture the field inside a scrolling panel or a dialog that hides its overflow. Ask where the suggestion list must live in the DOM to be visible and on top.
Hint 4
Popups inside popups: A dropdown can open inside a dialog that is itself a layer. Think about what has to coordinate stacking order, and which layer the Escape key should close.
Hint 5
Draw the timeline: Sketch a burst of fast keystrokes followed by a pause, and mark when requests fire under each technique.
Hint 6
One more character: When the user extends a query by one character, think about what a cached result for the shorter query can and cannot tell you.
Hint 7
Close is not equal: Ask when results for a query that is merely similar to the current one would mislead the user.
Hint 8
What consumers will change: List what a consuming team will want to change (data source, item rendering, selection handling) and find the narrowest props that allow it.
Hint 9
Exports are promises: Every exported component is an API you must keep stable. Decide which pieces consumers need to compose, and which would only let them break the component's invariants.
Follow-up questions
- How would you load more results as the user scrolls the suggestion list, and what happens if the query changes mid-scroll?
- How would you test the component, including debounce timing, out-of-order responses and accessibility?
- What changes if the page is rendered on the server?
- How should the popup behave on a phone, where the on-screen keyboard covers much of the screen?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.