“Build a filter sidebar” is a frontend machine-coding question that comes up in e-commerce (Amazon), SaaS dashboards (Linear, Jira), and admin tools. The interview probes URL state sync, debouncing, multi-selection patterns, and the experience of a shared link reproducing exactly the sender’s view.
Clarify scope
- What filter types? (single-select, multi-select, range slider, text search, date range)
- How many filter sections? (10–30 in real apps)
- Server-side filtering or client-side? (both is common)
- Share-by-URL required? (yes for most products)
- Mobile layout? (slide-over sheet vs persistent sidebar)
The state model
One canonical state object. Every filter type maps to a primitive or array:
{
query: "macbook",
category: ["laptops", "accessories"],
brand: ["apple"],
priceMin: 500,
priceMax: 2000,
inStock: true,
sort: "price_asc",
page: 2
}
This object is the source of truth. URL is its serialized form. Filter UI reads from it; mutations produce a new state.
URL serialization
Two common formats:
- Query string:
?category=laptops&category=accessories&priceMin=500— readable, cap on URL length - Compact encoded: base64 of JSON, single param — short, opaque, harder to debug
Query string is the right default. Use a library like qs for arrays.
Sync direction
- State change → push to URL (replace, not push, to avoid back-button noise on every keystroke)
- URL change → parse to state (on initial load, on browser back/forward)
Trap: do not create a loop. Mutate URL only when state changes meaningfully; mutate state only when URL changes from external source (browser nav).
Debouncing
- Free-text search: debounce 200–300 ms before updating URL and refetching
- Range slider: debounce on drag-end, not on every move
- Checkbox toggles: immediate
- Number inputs: debounce 400 ms
Why debounce: each URL update can trigger a server fetch and a history entry. Save both.
Multi-select pattern
- Each option as a checkbox
- “Show more” reveals additional options
- Counts per option (“Apple (43)”) if backend provides facets
- “Clear all” button per section
- Aggregate “Clear filters” at the top
Range slider
- Two thumbs, draggable
- Manual numeric inputs as alternative
- Histogram of distribution behind the slider (Amazon-style)
- Snap to reasonable steps
Active filter chips
Show selected filters as removable chips at the top:
- “Category: Laptops × Accessories”
- “Price: $500 – $2000”
- X button removes that specific filter
This is the highest-leverage feedback element. Users get lost without it.
Empty / loading states
- Loading: skeleton results, do not blank the list
- Empty: “No results match your filters. Clear all“
- Stale: subtle indicator while a debounced fetch is pending
Mobile considerations
- Slide-over sheet or full-screen modal
- Sticky “Apply” and “Clear” footer
- Apply on close, not on every change (saves taps and renders)
- Keyboard does not cover the active input
Accessibility
- Each section is a
<fieldset>with<legend> - Checkboxes have proper labels
- Range slider follows the WAI-ARIA slider pattern
- Filter chips: each is a button with “Remove filter X” label
- Live region announces result count after a change
Performance
- Server-side filtering for the result list; client-side for facet counts where small
- Use React.memo on filter section components
- Avoid re-rendering the whole sidebar on every change — split into independent sections
Edge cases interviewers love
- User shares URL with a filter the recipient doesn’t have access to (e.g., a deleted category) — graceful fallback
- User pastes a URL with too many filters — handle URL length
- Browser back/forward should restore exact state
- User clears all filters then hits back — restore the previous filters
- Filter combinations that produce zero results — show actionable empty state
What separates senior from staff
Senior: state model + URL sync + debounce. Staff: handles the share-by-link case carefully (versioning the URL schema), discusses faceted search server expectations, and handles the bidirectional state-URL sync without infinite loops.
Frequently Asked Questions
Library options for production?
nuqs (React) is excellent for URL-state sync. tanstack-router has built-in support. For older stacks, react-router’s useSearchParams plus your own debouncing.
How do I version the URL schema?
Add a v param. On parse, check version; if older, migrate. Simple. Keep migrations in code; do not break old URLs.
What about indexing for SEO?
Filtered URLs should be canonicalized to a base URL with rel=canonical unless they are SEO-strategic landing pages. Otherwise duplicate-content concerns arise.