Katkat

Build a Virtualized React Spreadsheet with Cell Formulas

HarveyFrontend Engineer · Technical Screen

Last updated

Design and implement a virtualized React spreadsheet component supporting editable cells, formula evaluation with string concatenation, cycle detection, and targeted re-rendering.

Task

Implement a performant React spreadsheet component (Spreadsheet) that renders a grid of cells (columns labeled A through Z, rows labeled 1 through N). The grid must support direct text entry, simple concatenation formulas referencing other cells, efficient virtualization for large row counts, and targeted re-renders.

Requirements

  1. Formula Evaluation & References:

    • A cell's raw input can be a literal string (e.g., Hello) or a formula beginning with =.
    • Support cell references and the string concatenation operator & (e.g., =A1 & " " & B1).
    • If a cell reference does not exist, evaluate it as #REF!.
    • If a circular dependency is detected (e.g., A1 references B1 and B1 references A1, or self-reference A1 = =A1), evaluate affected cells as #CYCLE!.
  2. Virtualization:

    • The grid must scale to 10,000 rows without rendering all DOM elements simultaneously.
    • Given a container with fixed height and width, compute and render only visible rows/columns plus a configurable overscan buffer.
  3. Re-render Optimization:

    • Changing a cell's raw value must not cause all visible cells to re-render. Only the edited cell and cells directly or transitively dependent on it should re-evaluate and re-render.

Component Interface

interface SpreadsheetProps {
  rowCount: number;
  colCount: number; // 1 to 26 (A-Z)
  cellWidth: number;
  cellHeight: number;
  viewportWidth: number;
  viewportHeight: number;
  overscan?: number; // default: 3
  initialData?: Record; // e.g., { A1: "Hello", B1: "World", C1: "=A1 & \" \" & B1" }
}

export const Spreadsheet: React.FC = (props) => {
  // Implementation
};

Example

Initial State:

  • A1 = "Hello"
  • B1 = "World"
  • C1 = "=A1 & \" \" & B1"
  • D1 = "=C1 & \"!\""

Output Rendered Values:

  • A1: Hello
  • B1: World
  • C1: Hello World
  • D1: Hello World!

Update:

  • User edits A1 to "Hi".
  • Only cells A1, C1, and D1 re-evaluate. B1 and all other cells remain untouched.
  • C1 evaluates to Hi World, D1 evaluates to Hi World!.

Constraints

  • rowCount <= 50,000
  • colCount <= 26 (columns A to Z)
  • Formula tokens are limited to cell references ([A-Z][1-9][0-9]*), string literals enclosed in double quotes ("..."), and the concatenation operator &.

Hints

Hint 1

Maintain a dependency graph (adjacency list of cell -> dependents) to determine which cells to recalculate and use cycle detection (DFS with visited states) during graph traversal.

Hint 2

For virtualization, track `scrollTop` in state or a ref, then calculate `startRow = Math.max(0, Math.floor(scrollTop / cellHeight) - overscan)` and `endRow` using the viewport height.

Hint 3

Separate raw input state from computed values, and wrap individual cell components in `React.memo` or use an external subscription store (e.g., Zustand/useSyncExternalStore) to isolate re-renders.

Follow-up questions

  • How would you ensure full keyboard navigation (arrows, Tab, Enter) and screen reader accessibility (`aria-rowindex`, `role="grid"`) when rows scroll out of the virtualized DOM?
  • How would you optimize bulk formula recalculations if a user pastes 1,000 cells at once to avoid deep recursion or UI thread freezing?
  • How would you extend the dependency model to support collaborative multi-user editing with conflict-free replicated data types (CRDTs) or Operational Transformation (OT)?