Loading...
Loading...
Senior · 45 min
Also asked as: Stripe Dashboard payments table, Airtable grid, Salesforce list view, Retool table, Datadog log explorer.
Read it once, then start the timer and work without scrolling.
Design an admin data table over 100,000+ rows with sorting, filtering, column resizing, inline editing, row selection, and CSV export.
Everything below is a model answer. You will get far more out of this drill by attempting it cold first — set the timer, talk through it out loud, and only then compare.
A 45-minute phase plan you can practise against a clock.
Interactive · Drill rehearsal timer
6:00
Time remaining in Clarify and scope.
What you should establish before drawing a single box.
Architecture, data, interfaces, and optimisations — the RADIO body.
The half of the answer most candidates skip entirely.
Naming how the system breaks, and what the user sees when it does, is the cheapest way to signal production experience. Work through these before the interviewer has to ask.
The interviewer will challenge you. Rehearse the concession, not just the defence.
Interviewer
Just load all 100k rows into the client once and do everything in memory. No pagination, no server round trips, and sorting is instant.
Model response
For a dataset that genuinely fits, that is the better design and I would take it — instant sort and filter beats a 300ms round trip every time, and it removes an entire class of pagination bugs. The reason 100k rows specifically does not fit is the payload rather than the algorithms: at a realistic 500 bytes to 1KB per row you are shipping fifty to a hundred megabytes, which is minutes on mobile, and parsing that JSON blocks the main thread for seconds before anything renders. Memory-resident objects will run several times the wire size. There is also a correctness dimension that is easy to miss — row-level permissions and freshness. Client-side filtering means the client holds rows the user may not be entitled to see, and a snapshot taken at load is stale the moment another admin edits something. My threshold is roughly ten thousand rows or a few megabytes; below it I load everything and my design collapses to something much simpler, and I would say so explicitly rather than build the complex version by reflex.
Interviewer
Virtualization breaks Ctrl+F, breaks native table semantics, breaks print, and makes the accessibility story much worse. That is a lot to give up for scroll performance.
Model response
Every one of those is a genuine regression and I do not think they can all be fully mitigated. Find-in-page over unrendered rows simply does not work, and the honest answer is to provide a real filter as the substitute rather than pretend the browser feature still functions. Print and export need a separate non-virtualized code path, which is real extra work. The accessibility piece is the one that is actually fixable: aria-rowcount and aria-rowindex let assistive technology report the true position in a hundred thousand rows even though thirty are mounted, and with a roving tabindex the keyboard model stays coherent. What makes the trade worth it is that the alternative is not a working table — a hundred thousand rows of real DOM is hundreds of megabytes and a browser that stops responding, so it is not a usable baseline. That said, I would try content-visibility: auto first, because it keeps nodes in the DOM and therefore preserves find-in-page while skipping most of the layout and paint cost. Full windowing is what I escalate to when profiling shows node count itself is the bottleneck.
Interviewer
Select-all-across-pages with an exclusion predicate is over-engineered. Just cap bulk actions at whatever the user can select on screen.
Model response
Capping is a legitimate product decision and it removes a genuinely tricky piece of state, so if the product agrees I would happily take it. The reason I usually cannot is that the whole point of an admin table is bulk operations at scale — an operator who needs to reassign twelve thousand matching records is not going to paginate through 240 pages, they will ask engineering to run a script, which is strictly worse for auditability and safety than doing it in the product. The part of your objection I do accept is that the exclusion model has a real hazard: the filter can match a different set at action time than it did at selection time, so the user might act on rows they never saw. My mitigation is to snapshot the filter with the selection, show the live matched count in the action bar, and require explicit re-confirmation if the count moved. If I could not build that safeguard properly, capping would be the more responsible choice.
Interviewer
Putting every filter in the URL produces enormous unreadable query strings and pollutes browser history with an entry per keystroke.
Model response
Both effects are real and I have seen tables produce two-kilobyte URLs that no one can share in a chat client without it wrapping badly. The history pollution is the easier problem: use replace rather than push while a filter is being actively edited and only push a history entry when the filter commits, so the back button undoes one meaningful step instead of one character. For length, I would keep the common case readable with short parameter names and a compact operator syntax, and for genuinely large filter sets fall back to saving the filter server-side and putting a short view id in the URL — which also gives you named saved views, something analysts ask for anyway. What I would not do is move filter state into component memory, because then a reload, a shared link, and the back button all lose the user's work, and that is the number one complaint I have heard about every internal tool I have worked on.
Specific signals an interviewer is listening for on this prompt.
Turn one attempt into retained skill.