Back to Machine Coding
Machine Coding
easy
mid

How would you build a grid toggle UI with immutable state and predictable re renders?

Build a grid of toggleable cells. The graded concepts are in the title: immutable state updates (copy before write, functional updater), modeling 2D structures (1D array with index math, or 2D array copied carefully), and predictable re-renders (stable keys, memoized cells, lifted state). Show you can update one cell without mutating or over-rendering.

6 min read·~30 min to think through

This prompt names exactly what it's grading: immutable state, 2D data structures, and predictable re-renders. Build a grid of cells that toggle on click, and demonstrate each.

1. Modeling the 2D structure

Two choices:

tsx
// 1D array + index math — easiest to update immutably
const idx = (r: number, c: number) => r * cols + c;
const [cells, setCells] = useState<boolean[]>(() => Array(rows * cols).fill(false));

// 2D array — intuitive shape, but immutable updates are more verbose
const [grid, setGrid] = useState<boolean[][]>(
  () => Array.from({ length: rows }, () => Array(cols).fill(false))
);

2. Immutable updates

Never mutate state. Copy the path you change:

tsx
// 1D version
const toggle = (i: number) =>
  setCells((prev) => prev.map((v, j) => (j === i ? !v : v)));

// 2D version — copy the outer array AND the affected row
const toggle2D = (r: number, c: number) =>
  setGrid((prev) =>
    prev.map((row, ri) =>
      ri === r ? row.map((v, ci) => (ci === c ? !v : v)) : row
    )
  );

Key points: functional updater (prev => ...) so it's correct under batching/concurrency; copy only what changed (the 2D version keeps unchanged row references stable — which helps memoization).

3. Predictable re-renders

Naively, toggling one cell re-renders every cell. Fix it:

tsx
const Cell = React.memo(function Cell({ on, onToggle }: CellProps) {
  return <button className={on ? "cell on" : "cell"} aria-pressed={on} onClick={onToggle} />;
});

For React.memo to actually work, the onToggle prop must be referentially stable — so pass the index and use a stable handler, or useCallback:

tsx
{cells.map((on, i) => (
  <Cell key={i} on={on} onToggle={useCallback(() => toggle(i), [i])} />
))}

(Cleaner: a single stable handler that reads data-index, avoiding per-cell closures entirely.)

Now only the toggled cell — whose on prop actually changed — re-renders. With the 2D version, keeping unchanged row references stable lets you memoize at the row level too.

4. Stable keys

Use a stable key. Index keys are acceptable here because cells never reorder or get inserted — only their value changes. If cells could be added/removed/reordered, you'd need an id.

What the interviewer is verifying

Graded conceptWhat they want to see
Immutable state.map/spread copies, functional updater, no mutation
2D structuresA sensible representation + correct nested immutable update
Predictable re-rendersReact.memo + stable handler refs, so one toggle ≠ N renders

Senior framing

The senior answer connects the three: immutability enables predictable re-renders — because unchanged cells keep the same prop references, React.memo can bail out. Mutation would break that. Demonstrating the memo + stable-callback combo (and explaining why it's needed, not just sprinkling useCallback) is the differentiator.

Follow-up questions

  • Why does React.memo need a referentially stable onToggle prop?
  • How does immutability enable render bailouts?
  • When are index keys acceptable and when not?

Common mistakes

  • Mutating a row in place in the 2D array (the outer .map still 'works' but breaks memo).
  • React.memo on cells but passing a fresh inline arrow each render — memo never bails.
  • Not using the functional updater.
  • Reaching for useCallback everywhere without understanding why.

Edge cases

  • 2D immutable updates must copy both the outer array and the changed row.
  • A single shared handler reading data-index avoids N useCallback closures.
  • Index keys break if cells can be reordered or inserted.

Real-world examples

  • Seat pickers, pixel editors, lights-out puzzles, spreadsheet-like grids.

Related questions