10 min read

Design Drills

Four interview prompts where the framework is the least interesting part. Work each one before reading the model answer.


How to run a drill

Frontend design rounds at senior level rarely ask "which state library?". They hand you something big, visual and fast, and watch how you reason. Answer every drill in the same five steps, out loud:

Give yourself 15 minutes per drill. Open the model answer only after you've written your own outline.


Drill 1: A million-point trading chart

"Design the price chart for a trading app. Users can see a live stock's ticks over the last trading day and zoom from the full day down to a few seconds. It must stay smooth on a mid-range laptop."

Model answer

Clarify. How many ticks per day for a busy stock? Let's assume up to 1,000,000. Live update rate: up to ~200 ticks per second at peak. Chart width: about 1,200 px. Candles and a line, plus volume bars. Desktop first.

Estimate.

  • 1M ticks × (8-byte time + 4-byte price + 4-byte volume) ≈ 16 MB in typed arrays. Fine in memory. As JS objects it would be several times that.
  • Full-day view: 1M ticks over 1,200 px is ~830 ticks per pixel column. Drawing raw ticks is pointless at that zoom.
  • At 200 ticks/s, the live edge changes every 5 ms. The screen needs it at most every 16 ms.

Pipeline.

  • The worker owns the socket and keeps raw ticks in a ring buffer.
  • It maintains a pyramid of pre-aggregated candles (OHLC + volume) at several resolutions, updating only the latest candle at each level per tick.
  • For the visible time range, it picks the coarsest level that still gives about 1–2 candles per pixel column, or min/max per column for the line view, so spikes stay visible. Only zoomed in to a few seconds does it send raw ticks.
  • It posts the result (a few thousand values) as transferable typed arrays.

Renderer. After aggregation the chart draws a few thousand shapes, so canvas 2D is enough: GPU rendering isn't needed when you never draw more points than pixels. Three layers:

  1. Grid and axes (redraw on zoom/pan).
  2. Candles/line/volume (redraw when data or view changes).
  3. Crosshair and hover tooltip (redraw on pointer move: tiny).

Axis labels and the price tag on the live edge can be DOM elements for crisp text.

Interaction. Wheel zoom around the cursor, drag to pan, all as a world-to-screen transform on the time axis. Hover uses binary search on the time array to find the nearest candle, no spatial index needed for 1D data. Updates are coalesced into one frame.

Accessibility. A summary for screen readers ("Last ₹1,842.50, up 1.2% today, high ₹1,860"), an aria-live region for significant moves, keyboard arrows to step between candles with an announced OHLC, and a data-table view.

Failure modes the interviewer will push on.

  • Reconnect after a drop: fetch the missing range by sequence number, then rebuild the affected candles.
  • Tab hidden: keep the ring buffer and pyramid updated in the worker but don't draw. Draw once on return.
  • "What if they want 10 years?": history comes from the server pre-aggregated by day or hour. The browser never holds 10 years of ticks.

Drill 2: A spreadsheet with a million cells

"Design the grid for a web spreadsheet: 10,000 rows × 100 columns, formulas, editing, frozen header row and first column, copy and paste."

Model answer

Clarify. Variable column widths and row heights? Usually widths yes, heights mostly fixed. Collaboration? Out of scope for now. Formula complexity? Typical business sheets.

Estimate. 1,000,000 cells, but most sheets are sparse: say 20% filled. A viewport shows about 40 rows × 15 columns = 600 cells.

Data model. A sparse map from cell key to { raw, value, format }, not a 10,000 × 100 array of objects. Formulas form a dependency graph: when A1 changes, recompute only the cells that depend on it, in topological order. Recalculation runs in a worker, so a big recalc never blocks typing.

Renderer. Two valid answers, and saying why is the point:

For an MVP, a 2D-virtualized DOM grid (rows and columns windowed, frozen header and first column via position: sticky) is quicker to build and accessible. At Sheets' scale and fidelity, a canvas grid wins on scroll smoothness and rendering control. Either way, the cell lookup at a pixel is pure arithmetic: prefix sums of column widths plus a binary search, then the row from y / rowHeight.

Editing. Never try to build a text editor inside canvas. When a cell enters edit mode, position one real <input> or <textarea> exactly over the cell, so IME, autocorrect, selection and clipboard work. Commit on Enter or blur.

Selection, copy and paste. Keep the selection as a range in the model and draw it on an interaction layer. For copy and paste, focus a hidden textarea and use the clipboard events, serialising the range as TSV (plus HTML for formatting) so pasting into other apps works.

Accessibility. role="grid" with aria-rowcount / aria-colcount, and the active cell exposed with aria-rowindex / aria-colindex. Arrow keys move the active cell and scroll it into view, and the active cell's address and value are announced.

Failure modes.

  • Circular references: detect cycles in the dependency graph and show #REF!-style errors.
  • A huge paste (100k cells): parse and apply in the worker, in chunks, with progress.
  • Fast scroll shows blank cells: overscan, plus drawing grid lines immediately and filling cell text a frame later.

Drill 3: A Figma-style infinite canvas

"Design the rendering architecture for a collaborative design tool: an infinite canvas with tens of thousands of shapes, text and images, smooth pan and zoom, selection and multiplayer cursors."

Model answer

Clarify. Shapes per document: up to ~50,000, with images. Target: 60 fps pan and zoom on a decent laptop. Multiplayer editing is in scope, but sync isn't the focus.

Scene model. A scene graph (pages → frames → groups → shapes) in world coordinates, owned by a document store that the sync layer (CRDT or OT) updates. Rendering reads from it and never owns it.

Renderer. WebGL for the canvas content: tens of thousands of shapes, constant pan and zoom, and effects like blur and blend modes are a natural fit for shaders. Batch shapes by material to keep draw calls low. Images become textures, managed against a GPU memory budget.

Staying fast at any zoom.

  • Culling through an R-tree: only shapes intersecting the viewport are drawn.
  • Level of detail: when zoomed far out, groups render as cached textures (tiles) instead of individual shapes. Re-render a tile only when something inside it changes.
  • Dirty-flag rendering: no redraw while nothing changes. During a drag, only the moving shapes' layer updates.
  • Rendering in a worker with OffscreenCanvas, so UI work on the main thread (panels, React) can't drop canvas frames.

Text. Render text with an SDF or glyph-atlas approach for display, but switch to a positioned DOM contenteditable while a text box is being edited, for IME, spellcheck and selection.

Hit testing. Pointer → world coordinates → R-tree query → exact geometry test on candidates (path containment for vectors, accounting for stroke width). For selection marquee, query the R-tree with the marquee rectangle.

Multiplayer cursors are DOM elements on an overlay layer, moved with transform, updated at a throttled rate and interpolated between updates.

Failure modes.

  • WebGL context loss: rebuild GPU resources from the document store.
  • GPU memory pressure from images: downscale textures to what the zoom level needs and evict unseen ones.
  • 50,000-shape document on a weak laptop: lean harder on LOD tiles, and cap texture resolution.

Drill 4: A live ops dashboard with 500 tiles

"Design a monitoring dashboard: 500 tiles (sparklines, KPI numbers, status badges) for services across regions, all live, viewed all day on wall screens and laptops."

Model answer

Clarify. Updates per tile: roughly once a second, some faster. Sparklines show the last 15 minutes. Viewed for hours without reload. Some users keep it in a background tab and want alerts.

Estimate. 500 tiles × 1 update/s = 500 updates/s, and most tiles are off-screen at any moment: a laptop shows maybe 40–60.

Pipeline.

  • One connection, owned by a worker. With many tabs, a SharedWorker or a BroadcastChannel leader can share it.
  • The client tells the server which tiles are visible (from an IntersectionObserver) and gets full-rate data for those and a slow summary for the rest. The server conflates: latest value per metric per interval.
  • Each metric has a ring buffer sized for 15 minutes, so memory is flat however long the dashboard runs.

Rendering.

  • The tile grid itself is virtualized or uses content-visibility, so off-screen tiles cost nothing.
  • One requestAnimationFrame loop for the whole page redraws only tiles that are visible and dirty. Never 500 setIntervals.
  • Sparklines on canvas 2D: ~900 points each (one per second over 15 minutes) is trivial for canvas. Don't give every tile its own WebGL context: browsers cap active contexts at around 16 per page. If you need GPU rendering, use one shared canvas behind the grid.
  • KPI numbers are DOM text at 2–5 Hz with tabular-nums and fixed widths, so nothing reflows.

Background tabs and alerts. When hidden, stop drawing but keep the worker evaluating alert rules. Fire a Notification or update the tab title on alerts. On return, draw once from the current buffers. No backlog replay.

Staleness and reconnection. Every tile shows data age, and tiles fade when their data is older than a threshold. Reconnect with backoff and jitter, and re-sync from a snapshot with sequence numbers.

Accessibility. Status uses icons and text, not colour alone. Status changes announce through one throttled aria-live region ("payments-eu: degraded"). The grid is navigable by keyboard, with each tile's current value as its accessible name.

Failure modes.

  • Eight hours on a wall screen: check the heap trend in the Performance monitor. Bounded buffers, no listeners added per update.
  • A burst after an incident (everything changes at once): conflation server-side, drop-don't-queue client-side, and a frame budget per loop iteration.
  • The wall screen's slow GPU: canvas 2D sparklines and DOM numbers degrade gracefully.

What every model answer has in common

MoveWhere it showed up
Never draw more than the pixels can showAggregation pyramid, min/max downsampling, LOD tiles
Only process what's visibleVirtualized grid, culling, visible-tile subscriptions
Bounded memory for unbounded streamsRing buffers everywhere
Keep the main thread for inputWorkers for sockets, recalculation and aggregation. OffscreenCanvas.
The right renderer per layerDOM for text and editing, canvas for charts, WebGL for dense scenes
Put back what custom rendering removesHit testing, keyboard navigation, aria-live, real inputs for editing

If you can say those six things with numbers attached, you can handle most frontend "design something big and fast" questions.


Go deeper