QuestionsSystem Design

Design a data table (sort, filter, pagination)

RADIOMediumSystem Design

Design a reusable data-table component that supports column sorting, filtering, and pagination over large datasets.

What it tests

API design for a flexible component, server- vs client-side data strategy, and accessibility.

Approach & answer

Requirements: columns with per-column sort and filter, pagination, potentially tens of thousands of rows, reusable across teams, keyboard-and-screen-reader accessible. Architecture — separate three concerns: (1) a headless data layer that owns query state {sortBy, sortDir, filters, page, pageSize} and returns rows; (2) presentational cells/headers driven by a column config array ({ key, header, render, sortable, filterable }) so consumers declare columns as data rather than JSX; (3) the shell that wires them. Data strategy is the key fork: for small datasets do sort/filter/paginate on the client (instant, no network); past a few thousand rows push all three to the server (the query state becomes URL/request params) so you never ship the whole set — and debounce filter input to avoid a request per keystroke. Keep query state in the URL so a filtered/sorted view is shareable and survives reload. Performance: virtualize rows (windowing) when rendering large pages, memoize the column config and row renderers, and use stable keys. Accessibility is non-negotiable and frequently the differentiator: use a real <table> with <th scope='col'>, put aria-sort on the sorted header, make sort controls real buttons, and announce result counts via a live region. Edge cases: empty state, loading skeletons, error state, sticky header, and column resize.

Use this technique when

Any 'design a table/grid/list-with-controls' prompt; deciding client vs server data handling; building a design-system table.

Complexity

Client sort O(n log n); server mode shifts cost off the client. Virtualize to bound DOM nodes.

Code

// Columns as data -> the table is generic and declarative.
const columns = [
  { key: 'name',  header: 'Name',  sortable: true,  filterable: true },
  { key: 'email', header: 'Email', sortable: false, render: r => <a href={'mailto:' + r.email}>{r.email}</a> },
];

// Single query-state object; server mode sends it as params.
const [query, setQuery] = React.useState({ sortBy: 'name', sortDir: 'asc', filters: {}, page: 1, pageSize: 25 });

// Accessible header: <th aria-sort="ascending"><button onClick={toggleSort}>Name</button></th>

References