๐๏ธ The Scene: The Interview Kickoff
Interviewer: "We need a reusable table component for our enterprise platform. It must handle millions of rows, sustain 100+ updates per 10ms (HFT style), and be flexible enough for 50+ different engineering teams. How do you approach this?"
Interviewee: "To build a truly 5/5 system, we can't treat this as a UI widget. We have to treat it as a Data Orchestration Engine. I'll break this down into three layers:
The Headless Core: Logic-only layer for sorting, filtering, and grouping.
The High-Frequency Pipeline: Handling the 'Thundering Herd' of WebSocket updates.
The Rendering Layer: Using Virtualization and GPU-accelerated CSS."
๐ ๏ธ Phase 1: The API & Data Contract (MVP 0)
To ensure 50 teams can use this, we use a Headless Pattern. The table doesn't own the UI; it provides the state.
The Column Definition
TypeScript
interface ColumnDef<T> {
id: string;
header: string | React.ReactNode;
accessorKey: keyof T | ((row: T) => any);
cell?: (value: any, row: T) => React.ReactNode;
width?: number; // In pixels
minWidth?: number;
}
interface TableSystemProps<T> {
data: T[];
columns: ColumnDef<T>[];
virtualize?: boolean;
}๐ Phase 2: The "Thundering Herd" (Handling 100ms Updates)
Interviewer: "How do you handle 100 WebSocket messages in 10ms without the UI locking up?"
Interviewee: "We must decouple Ingestion from Rendering. If we call setState for every message, the React reconciler will crash. We use a RequestAnimationFrame (rAF) Buffer."
The Working Code: High-Frequency Update Manager
TypeScript
import { useRef, useState, useEffect } from 'react';
export function useTableDataEngine<T extends { id: string }>(initialData: T[]) {
const [displayData, setDisplayData] = useState(initialData);
const bufferRef = useRef<Map<string, T>>(new Map());
const rafId = useRef<number | null>(null);
// High-performance update function
const pushUpdate = (incoming: T) => {
// 1. Accumulate updates in a Map (O(1) lookup/write)
bufferRef.current.set(incoming.id, incoming);
// 2. Schedule a single render per 16.6ms frame
if (!rafId.current) {
rafId.current = requestAnimationFrame(() => {
setDisplayData((prev) => {
const next = [...prev];
bufferRef.current.forEach((val, id) => {
const idx = next.findIndex(r => r.id === id);
if (idx !== -1) next[idx] = { ...next[idx], ...val };
});
bufferRef.current.clear();
rafId.current = null;
return next;
});
});
}
};
return { displayData, pushUpdate };
}๐๏ธ Phase 3: System Design & Architecture
To scale, we move heavy computation (sorting 100k rows) off the main thread.
Code snippet
graph TD
WS[WebSocket / API] --> Ingest[Data Ingestor]
Ingest --> Worker[Web Worker: Sorting/Filtering]
Worker --> Buffer[rAF Update Buffer]
Buffer --> React[React Component Tree]
React --> Virtual[Virtualization Layer]
Virtual --> GPU[GPU Rendering: transform/opacity]โก Phase 4: Extreme Performance Optimizations
1. Column Resizing via CSS Variables
Interviewer: "Resizing columns usually triggers a massive re-render. How do you avoid it?"
Interviewee: "I avoid React state for the resize movement. I define the table grid using CSS Variables. When the user drags, I update the variable on the parent container directly. The cells adjust via the browser's layout engine, bypassing React's diffing entirely."
TypeScript
// Apply this to the Table Container
const tableStyle = {
display: 'grid',
gridTemplateColumns: `var(--col-1-width) var(--col-2-width) auto`,
};2. Atomic "Flash" Updates
For HFT, we don't want the whole row to re-render when one price changes. We use React.memo with a custom comparison and a ref to trigger CSS animations for "Price Up/Down" flashes.
๐ฑ Phase 5: Responsive & Packaging Strategy
Responsive "Card" Switch
We use display: contents and media queries. On mobile, the <table> structure is flattened into flex-direction: column to create a card-like view.
Packaging (The Staff Move)
Tree Shaking: Use
sideEffects: falseinpackage.json.A11y: Ensure
role="grid",aria-rowcount, and keyboard navigation (Arrows to move focus).
In a FAANG interview, this shows you care about both Off-Main-Thread computation and Inclusivity.
๐ง Phase 6: The Web Worker "Brain"
Interviewer: "Sorting 100k rows can take 100ms+. That's a dropped frame. How do you fix it?"
Interviewee: "I offload the sorting logic to a Web Worker. The Main Thread only handles the UI; the Worker handles the math. We use a Stable Sort algorithm to ensure that if two rows have the same value, their relative order stays the same."
The Worker Logic (tableWorker.ts)
TypeScript
// Off-main-thread sorting logic
self.onmessage = ({ data: { rows, sortConfigs } }) => {
const sorted = [...rows].sort((a, b) => {
for (const { id, direction } of sortConfigs) {
const valA = a[id];
const valB = b[id];
if (valA !== valB) {
const modifier = direction === 'asc' ? 1 : -1;
return valA > valB ? modifier : -modifier;
}
}
return 0;
});
self.postMessage(sorted);
};โฟ Phase 7: Accessibility (The 5/5 "Hidden" Requirement)
Interviewer: "How does a screen-reader user navigate a virtualized table with 1 million rows?"
Interviewee: "Standard tables rely on the DOM order. Virtualized tables break that. To solve this, we use ARIA Grid roles and Manual Focus Management."
Key A11y Implementation:
aria-rowcount: Tell the screen reader there are 1,000,000 rows, even if only 20 are in the DOM.aria-rowindex: Explicitly label each virtual row (e.g., "Row 500,000").Keyboard Nav: Use a
onKeyDownhandler to intercept Arrow keys and manually calculate which virtual cell should receive focus.
๐จ Phase 8: Visual Polish & UX (The "Delight" Layer)
A Staff Engineer knows that a table is only as good as its UX.
Skeleton States: While the Web Worker is sorting, we show a blurred "Loading Overlay" so the user knows the system is thinking.
Sticky Columns: Use
position: stickyon the first/last columns. Staff Tip: Mention thatstickyperformance is better than JS-calculated offsets because it's handled by the browser's compositor.Empty States: Always design for "No results found" or "Filter cleared all rows."
๐ The Final Architectural Blueprint
Component Hierarchy
Code snippet
graph TD
A[TableProvider] --> B[TableContainer]
B --> C[HeaderGroup]
B --> D[VirtualList]
D --> E[RowNode]
E --> F[CellRenderer]
subgraph State
G[Sort/Filter State]
H[Column Visibility]
I[Scroll Offset]
endReal-World "High-Frequency" Scenario (HFT)
App: A Trading Terminal like Bloomberg.
Pain Point: 1,000 price updates/sec.
Staff Solution: Use Canvas-based Rendering for the cells if the DOM overhead becomes too high. In Canvas, you can draw 10,000 cells in a single paint call, bypassing the "Tree" structure of the DOM entirely.
๐ Summary: The Staff-Level Checklist
Feature | Solution | Why it wins (5/5) |
Data Ingestion | rAF Batching | Prevents UI thread blocking/jank. |
Large Datasets | Row Virtualization | Keeps DOM nodes constant regardless of data size. |
Heavy Logic | Web Workers | Moves O(N log N) sorting off the main thread. |
Resizing | CSS Variables | $O(1)$ update to the DOM style, no React re-renders. |
HFT Performance | Atomic Refs | Bypasses React for micro-animations (flashes). |