Recommended preparation

Try first

Frame your answer before reading the reference

Senior20 min first pass

Candidate prompt

Design a customizable dashboard with pointer and keyboard movement, grid constraints, optimistic layout persistence, versioned migrations, and responsive reflow.

Constraints

  • Keep client, server, and rendering ownership explicit.
  • Cover loading, failure, recovery, and accessible interaction states.

Helpful before you start

  • Client-side state management
  • Accessible UI fundamentals

Make these three decisions explicit

  1. State and ownership boundaries
  2. API and event contracts
  3. Performance and failure tradeoffs

Requirements

Frame this as a frontend layout editor, not as a generic drag-and-drop feature.
What to lock down in the first 5 minutes
AreaQuestion to askDefensible decision signal
RequirementsHow many widgets, which widget types, and can users add/remove/reorder them?Defines whether you need a reusable layout engine or a narrow page-specific editor.
Grid/layout data modelFixed 12-column grid, free-form canvas, or breakpoint-specific layouts?Keeps positions in logical grid units so snapping, resize, and persistence are deterministic.
Drag and resize ownershipDoes the layout container own all pointer and keyboard interaction, or can widgets opt in?Prevents widget content from mixing chart state with drag math.
Collision and snappingWhen a candidate rectangle overlaps another widget, do we reject, push, swap, compact, or allow overlap?Turns a vague drag feature into explicit product behavior.
PerformanceHow many widgets and how heavy are they during drag?Separates the hot pointermove/rAF loop from slower framework state commits.
PersistenceSave automatically on drop, on explicit Save, or sync to a server per user?Clarifies optimistic local layout, revision conflicts, and schema migrations.
AccessibilityCan users move and resize widgets without a pointer device?Adds keyboard move/resize controls, focus management, and an alternate layout editor.
Interview opening
A strong opening is: model every widget as a rectangle on a logical grid, use a layout engine for snapping and collision resolution, keep pointermove work outside expensive framework rerenders, and persist one snapshot against its authoritative revision after the interaction commits.
Early trade-offs to state explicitly
DecisionOption AOption BPractical choice
CSS Grid vs absolute positioningCSS Grid is semantic and good for static responsive tracks, but drag previews are harder because grid placement triggers layout.Absolute positioned containers with transform previews are easier for interactive movement, but you must own all grid math.Use a logical grid model and render widgets as positioned boxes; use transforms during drag and commit final grid coordinates.
Logical coordinates vs raw pixelsGrid coordinates like x/y/width/height survive row height, gap, and breakpoint changes.Raw pixels feel direct but make snapping, persistence, and responsive remapping brittle.Persist logical coordinates and derive pixels from container width, column count, row height, and gap.
Save on every move vs commitSaving every move protects against crashes but adds JSON/stringify/storage or network work to the hot path.Saving on drag/resize end is cheaper but needs local optimistic state until persistence finishes.Update local layout optimistically during interaction, then persist on pointerup or explicit Save.
Deterministic grid/snap/collision example

Grid: 6 columns, rowHeight = 80px, gap = 8px
Widget A: { id: 'a', x: 0, y: 0, width: 2, height: 2 }
Widget B: { id: 'b', x: 2, y: 0, width: 2, height: 2 }
Widget C: { id: 'c', x: 4, y: 1, width: 2, height: 1 }

Before
row 0: A A B B . .
row 1: A A B B C C
row 2: . . . . . .

User drags A right by 2 columns.
Candidate A: x = 2, y = 0, width = 2, height = 2
Collision: A overlaps B at columns 2-3, rows 0-1.
Resolution policy: push the blocking widget down, then compact if there is empty space.

After
row 0: . . A A . .
row 1: . . A A C C
row 2: . . B B . .
row 3: . . B B . .
Unreliable shortcut vs defensible decision
Evaluation areaUnreliable shortcutDefensible decision
ScopeUse a drag-and-drop library and save positions.Define grid rules, collision policy, resize constraints, persistence timing, keyboard support, and responsive layouts before selecting a library.
State modelEach widget stores its own left/top CSS.The dashboard owns one layout array in grid units; widget content stays separate from layout math.
Interaction performanceUpdate React/Angular state on every mousemove.Pointermove records the latest candidate; requestAnimationFrame applies a cheap transform and framework state commits at lower-frequency boundaries.
PersistenceWrite localStorage whenever anything changes.Persist a compact snapshot on drop or Save, using schemaVersion for format migration and revision for save conflicts.
AccessibilityDrag handles are enough.Provide keyboard move/resize steps, focusable handles, live position feedback, and a non-drag layout editor fallback.
Model
Grid rectangles
Widget geometry plus snapshot schemaVersion and revision.
Hot path
pointermove -> rAF
Keep framework rerenders and persistence out of every move event.
Commit point
pointerup / Save
Finalize collisions, emit layout change, and persist.
Scope checkpoint
This prompt is about frontend interaction architecture: a deterministic grid layout model, explicit collision behavior, smooth rendering boundaries, resilient persistence, and accessible controls.

Frontend boundary

The browser owns pointer and keyboard interaction, preview geometry, collision feedback, optimistic layout overlays, responsive projection, and undo. Server-side layout validation and durable storage stay behind abstract revision-aware snapshot and save contracts, with schemaVersion reserved for format migration.

Architecture

The architecture should make one boundary obvious: the dashboard layout system owns grid math and interactions; individual widgets render charts, tables, or KPIs without knowing collision rules.
Frontend building blocks
PieceResponsibilityDo not let it own
Dashboard shellLoads default or saved layout, owns grid config, renders positioned widget containers, and coordinates persistence.Per-widget business data, chart internals, or low-level pointer math.
Layout engineConverts candidate rectangles into legal grid positions, applies snapping, bounds, min sizes, collision resolution, and compaction.DOM events or framework component lifecycle.
Interaction controllerUses pointerdown/move/up, pointer capture, keyboard controls, and resize handles to produce grid deltas.Persistence, server sync, or widget content state.
Render layerMaps grid rectangles to CSS variables, transform previews, placeholders, and committed positions.Collision decisions or storage format decisions.
Persistence adapterSerializes snapshots with schemaVersion and revision, then handles save failures, conflicts, and format migrations.Per-frame visual updates.
Render strategy trade-off
StrategyWorks well forCostUse here
CSS Grid placementStatic or low-frequency dashboard layout where semantic grid tracks matter.Changing grid-row/grid-column during drag can force layout and move many items.Useful for the committed layout if the dashboard is small and CSS handles breakpoints.
Absolute positioned gridInteractive drag/resize where the app derives left/top/width/height from grid coordinates.You own collision math, bounds, and responsive remapping.Best default for a draggable editor because preview movement can use transform.
Transform-only drag previewHigh-frequency pointer movement while the user drags one widget.The visual preview can temporarily differ from committed DOM order/layout.Use during interaction, then commit final grid coordinates on pointerup.
Interaction flow
  1. Start
    Pointerdown on a handle records the starting pointer, widget rectangle, grid config, and scroll/container bounds; setPointerCapture keeps events flowing even if the pointer leaves the handle.
  2. Preview
    Pointermove stores the latest pointer position and schedules one requestAnimationFrame. The frame computes a candidate grid delta and applies a cheap transform or placeholder update.
  3. Resolve
    The layout engine clamps bounds, snaps to cells, enforces minWidth/minHeight, and resolves collisions according to the product policy.
  4. Commit
    Pointerup releases capture, finalizes the layout array, emits one commit against the base revision, and persists a snapshot whose schemaVersion is independent.
  5. Recover
    If persistence fails, keep the optimistic local layout visible, show a non-blocking save error, and retry or let the user save again.
Collision strategies
StrategyUser experienceImplementation costWhen to choose it
RejectThe dragged widget cannot enter occupied cells.Low; just test overlap and clamp.Dense dashboards where predictable constraints matter more than fluid rearranging.
Push and compactBlocking widgets move down or sideways and empty gaps are compacted.Medium to high; needs deterministic ordering to avoid loops.Dashboard builders where users expect automatic rearrangement.
SwapThe active widget trades places with the widget it overlaps most.Medium; simpler than full compaction but can feel jumpy.Small grids with similarly sized tiles.
Allow overlapWidgets can stack or partially cover each other.Low layout cost but higher UX/accessibility cost.Canvas-like tools, not most analytics dashboards.
Versioned layout ownership
Do not describe a backend dashboard service. The important frontend design is the client layout engine, interaction controller, render boundary, persisted snapshot, and accessible editing surface.

Worked example: remote layout update during a drag

A user drags widget w7 from column one to column three based on layout revision 12 while another tab saves revision 13. Pointer movement must stay local and fast, then the final command must reconcile the conflict.
Scenario walkthrough
EventStore changeVisible UIInvariant
Drag startsSnapshot revision 12 and create a transient preview geometry.The widget follows a transform preview with clear drop targets.Pointermove does not persist.
Remote revision 13 arrivesQueue the server base without replacing the active preview.The drag does not jump under the pointer.Interaction state stays coherent.
Drop occursSolve constraints against the newest known base and send one command.Show pending placement while keeping undo available.Only settled geometry is persisted.
Server conflictsMerge revision 13 and recompute or roll back the overlay.Explain the changed layout and restore focus to w7.Authoritative revision wins without losing context.

Data

The data model should make every visual decision reproducible: a widget is a rectangle in grid units, not a pile of DOM styles. Pixels are derived from the grid config at render time.
type LayoutSchemaVersion = 2;
type Breakpoint = 'desktop' | 'tablet' | 'mobile';

interface GridConfig {
  columns: number;
  rowHeight: number;
  gap: number;
}

interface WidgetLayout {
  id: string;
  x: number;
  y: number;
  width: number;
  height: number;
  minWidth: number;
  minHeight: number;
}

interface DashboardSnapshot {
  schemaVersion: LayoutSchemaVersion;
  revision: number;
  layouts: Partial<Record<Breakpoint, WidgetLayout[]>>;
  updatedAt: string;
}

interface InteractionState {
  activeId: string | null;
  mode: 'idle' | 'drag' | 'resize' | 'keyboard-move' | 'keyboard-resize';
  baseRevision: number;
  startRect?: WidgetLayout;
  candidateRect?: WidgetLayout;
}

interface PendingLayout {
  commandId: string;
  basedOnRevision: number;
  breakpoint: Breakpoint;
  patch: WidgetLayout[];
}
What each field buys you
FieldWhy it existsFailure if missing
idStable identity across rerenders, persistence, and widget data joins.A reordered dashboard can attach saved size/position to the wrong widget.
x, yLogical grid origin for snap, collision, and responsive remapping.Raw pixels become stale when container width or column count changes.
width, heightLogical rectangle size in columns/rows.Resize logic cannot enforce grid-aligned collisions.
minWidth, minHeightPrevents charts, controls, or tables from becoming unusable.Resize handles can create broken or inaccessible widget states.
schemaVersionSelects the migration for a persisted document format.Old saved shapes either break silently or require risky one-off parsing.
revisionProvides a monotonic concurrency precondition for saves.A tab can overwrite a newer layout without detecting conflict.
Persistent vs transient state
StateExamplesWhere it lives
Persistent layoutschemaVersion, revision, breakpoint key, and widget rectangles.localStorage or server snapshot; restored on page load.
Transient interactionactiveId, pointer start, candidate rectangle, keyboard step, placeholder position.Component/controller memory only; never persisted.
Widget runtime stateChart filters, table sort, data loading, error state.Owned by widget or data layer; not mixed into layout collision math.
Persistence statussaving, saved, failed, or stale authoritative revision.Dashboard shell or sync adapter; visible to users without blocking drag.
Saved layout lifecycle
  1. Load
    Read the newest compatible snapshot for the user and breakpoint; fall back to a default layout if missing or invalid.
  2. Migrate
    If snapshot.schemaVersion is older, run the matching document migration, clamp invalid sizes, and fill missing breakpoint layouts before rendering.
  3. Edit locally
    During drag/resize, update candidate layout locally and keep visual feedback immediate.
  4. Commit
    On pointerup, keyboard confirm, or explicit Save, write a new snapshot and mark the local layout as pending/saved.
  5. Resolve conflict
    If the save returns a newer authoritative revision, merge or roll back according to the declared conflict policy; never confuse this concurrency revision with schema migration.
Responsive layout policy
PolicyBenefitTrade-off
One desktop layout scaled downSimple persistence and predictable desktop editing.Mobile can become a cramped stack unless you derive a separate mobile order.
Separate layouts per breakpointBest user control for desktop, tablet, and mobile.More snapshot data and migration paths.
Desktop editable, mobile read-only stacked orderGood for dashboards where drag/resize is mainly a desktop workflow.Mobile users need a separate reorder/settings interface if editing is required.
Data-model signal
Persist the smallest stable layout contract. Do not persist pointer positions, DOM measurements, isDragging flags, or chart runtime state inside the layout snapshot.

Snapshot, breakpoint, interaction, and pending-save ownership

Represent canonical widget rectangles in logical grid coordinates, separate from transient pixel preview. A DashboardSnapshot carries schemaVersion for format migration and monotonic revision for concurrency. DragSession stores origin, pointer capture, candidate rectangle, and collision result. PendingLayout overlays a command on the authoritative document until reconciliation.
Client model
RecordKey fieldsOwner
LayoutDocumentschemaVersion, revision, widgets, columns, breakpointsServer cache
WidgetRectid, x, y, width, height, constraintsCanonical grid
DragSessionwidgetId, origin, candidate, pointerIdLocal interaction
PendingLayoutcommandId, basedOnRevision, breakpoint, patchOptimistic overlay

Interface

The public API should hide pointer math and collision internals. Consumers provide widgets and receive committed layout snapshots; the layout system owns movement, resize, keyboard editing, and persistence hooks.
type LayoutSaveReason = 'drag' | 'resize' | 'keyboard' | 'reset' | 'save' | 'undo';

interface LayoutCommit {
  commandId: string;
  basedOnRevision: number;
  breakpoint: Breakpoint;
  layout: WidgetLayout[];
  reason: LayoutSaveReason;
}

interface DashboardLayoutProps {
  breakpoint: Breakpoint;
  grid: GridConfig;
  snapshot: DashboardSnapshot;
  editable: boolean;
  renderWidget: (widget: WidgetLayout) => unknown;
  onLayoutCommit: (commit: LayoutCommit) => void;
  onLayoutPreview?: (preview: { breakpoint: Breakpoint; layout: WidgetLayout[] }) => void;
  onSaveStatusChange?: (status: 'idle' | 'saving' | 'saved' | 'failed' | 'conflict') => void;
}

interface LayoutClient {
  saveLayout(input: LayoutCommit & { signal: AbortSignal }): Promise<DashboardSnapshot>;
  getLayout(input: { signal: AbortSignal }): Promise<DashboardSnapshot>;
}

interface DashboardLayoutHandle {
  resetLayout(): void;
  undo(): void;
  focusWidget(id: string): void;
  moveWidgetByKeyboard(id: string, delta: { x: number; y: number }): void;
  resizeWidgetByKeyboard(id: string, delta: { width: number; height: number }): void;
}
API decisions
Interface choiceGood versionAvoid
Commit callbackonLayoutCommit fires once after drag, resize, keyboard confirm, reset, or explicit Save.Calling parent on every pointermove and forcing app-wide rerenders.
Preview callbackOptional and documented as throttled/rAF-batched for analytics or ghost UI only.Treating preview updates as durable layout state.
Widget renderingrenderWidget receives layout metadata but does not receive pointer events or collision internals.Letting every widget implement its own drag and resize behavior.
Imperative handleExpose focused operations like reset, focusWidget, keyboard move, keyboard resize.Exposing raw internal maps, DOM nodes, or collision queues.
Persistence statusReport saving/saved/failed so the page can show non-blocking feedback.Blocking pointer interactions while a layout save request is pending.
Keyboard and accessible alternatives
NeedImplementationWhy it matters
Move without pointerFocusable widget handles support arrow keys for one-cell moves and Shift+Arrow for larger moves.Keyboard users can reorder the dashboard without drag gestures.
Resize without pointerA focused resize handle or menu updates width/height in grid steps and announces the new size.Resize is not hidden behind mouse-only corners.
Screen reader feedbackAnnounce position and size changes in a polite live region, e.g. Revenue chart moved to column 3 row 2.Users get confirmation without needing to infer visual movement.
Fallback editorOffer a list/table editor for order, width, and height when drag is hard on mobile or assistive tech.Complex spatial editing remains usable across devices.
API signal
A strong API makes the hard part look boring: layout in, committed layout out, widget renderer as a slot, and accessible controls included by the layout system.

UI-facing contract

saveLayout accepts a basedOnRevision precondition, command ID, breakpoint, reason, and the complete logical layout for that breakpoint. It returns a complete snapshot with the authoritative revision or a conflict response resolved by fetching that snapshot. schemaVersion is used only to migrate document shape. Components receive grid-to-pixel projections and intent callbacks; they never write storage from pointermove.
From drag intent to revision-aware reconciliation
  1. Capture
    Start one drag session from pointer or keyboard intent and preserve the authoritative origin.
  2. Preview
    Compute transforms and collision feedback without rewriting every widget entity.
  3. Commit
    On drop, validate constraints and send one breakpoint-scoped layout command against the base revision.
  4. Reconcile
    Replace or remove the overlay from the returned authoritative revision and restore meaningful focus.

Optimizations

The performance answer should name the expensive boundary. The costly mistake is not drag itself; it is doing layout reads, collision scans, widget rerenders, JSON serialization, storage writes, or network mutations for every pointermove.
Hot-path boundaries
BoundaryWhat happens thereWhat to avoidBetter approach
pointermoveReceives many events per second and records latest pointer coordinates.setState/store dispatch plus collision and persistence work on every event.Store latest input in a controller/ref and schedule one rAF if none is pending.
requestAnimationFrameRuns at most once per frame and computes the visible candidate rectangle.Reading layout after writing styles, causing forced reflow.Read container metrics before the interaction, then write transform/placeholder styles together.
Framework commitUpdates durable layout state and rerenders containers.Rerendering every heavy chart/table on each tiny pointer delta.Commit on grid-cell change, drag end, or a throttled preview; memoize widget content.
PersistenceSerializes layout and writes localStorage or calls an API.JSON.stringify, storage writes, and fetch requests inside the move loop.Persist on pointerup/keyboard confirm/Save with retry and visible save status.
Optimization trade-offs
TopicChoiceTrade-off
rAF batchingBatch visual updates in requestAnimationFrame.Keeps frames predictable, but the code must handle dropped intermediate pointer events by using the latest input.
Collision indexingBucket widgets by row/column or active region.Reduces scans for large dashboards, but adds bookkeeping and invalidation complexity.
Transform previewsMove the active widget with transform before committing grid position.Smooth preview, but final layout still needs a committed rectangle and accessible DOM order policy.
Widget memoizationKeep chart/table internals stable while only containers move.Improves drag smoothness, but stale props bugs are possible if widget data dependencies are hidden.
VirtualizationRender only visible widgets in very tall dashboards.Useful for large dashboards, but drag across virtual boundaries needs placeholder and scroll handling.
Optimistic layout vs persisted layout
ApproachBenefitRiskMitigation
Optimistic local layoutThe dashboard feels instant after drop.Server save can fail or another device can have a newer layout.Show save status, retry, and keep an authoritative monotonic revision in the snapshot.
Wait for server before applyingUI always reflects persisted truth.Drag/drop feels laggy and failure blocks editing.Use only for highly regulated dashboards; otherwise prefer optimistic commit.
LocalStorage onlySimple and works offline on one device.No cross-device sync and data can be cleared.Good baseline for interview scope; mention server sync as an extension.
Server-backed per-user layoutCross-device restore and team/device policy support.Needs conflicts, auth, migrations, and failure states.Persist on commit, not per frame; include schemaVersion for migration, revision for conflicts, and one declared conflict policy.
Performance validation path
  1. Baseline
    Record dragging and resizing 20-50 widgets with charts/tables loaded.
  2. Inspect frames
    Look for long tasks, forced reflow, excessive component updates, layout/paint spikes, and INP regressions.
  3. Move work
    Move storage, analytics, server sync, and heavy collision compaction out of pointermove.
  4. Protect widgets
    Memoize heavy widgets and move only their containers or a drag preview layer.
  5. Verify edges
    Test dense collisions, fast pointer movement, window resize during edit, failed saves, keyboard moves, and mobile stacked layouts.
Layout interaction and synchronization edge cases
Edge caseFrontend behavior
Window resizes mid-dragEither freeze the grid metrics until drop or cancel/recompute safely; do not mix old pointer math with new columns.
Widget removed while layout existsIgnore unknown saved ids and migrate the snapshot on next save.
Breakpoint changesChoose separate breakpoint layouts or derive a stacked mobile order from desktop.
Corrupt saved JSONDiscard it, restore defaults, and avoid blocking the page.
Keyboard and pointer edits raceUse one active interaction lock so two controllers cannot mutate the same layout at once.
Performance signal
Identify the exact expensive update boundary. Keep pointer input cheap, batch visual work in rAF, commit durable layout intentionally, and persist only after interaction end.

Conflict, viewport, and interaction recovery

Failure modes
FailureResponseInvariant
Pointer capture is lostCancel preview and restore origin.No half-dragged state remains.
Save response is uncertainRetain command identity and query the authoritative layout.Retry cannot double-apply.
Breakpoint changesProject the logical layout through explicit responsive policy.Stored desktop pixels are not reused blindly.
Keyboard move collidesAnnounce the blocked position and preserve focus.Pointer and keyboard share constraints.

Accessibility behavior

Every widget has a visible handle and keyboard move and resize commands with understandable increments. Announce the candidate position and successful placement through a restrained status region. Drag is never the only path; focus remains on the widget, and reduced motion removes decorative movement without hiding collision or selection state.

Rollout and measurement

Ship read-only layout projection, then pointer preview, keyboard operations, and persistence as separate flags. Profile pointer latency with real widget contents and monitor failed saves, conflict recovery, undo success, clipped layouts, focus loss, and mobile reflow.
dashboard widgets draggable resizable answer rubric
  • Must — Store normalized widget identity separately from responsive layout placement.
  • Must — Preserve a versioned layout and resolve drag or resize updates against the current revision.
  • Strong signal — Keep pointer preview local and commit one semantic placement when interaction ends.
  • Strong signal — Provide keyboard movement, collision feedback, and stable focus after reflow.
  • Expert stretch — Describe schema migration and measured rendering limits for large configurable boards.
  • Red flag — Writing every pointermove to shared persistence or using array position as widget identity.

Use the Question Library for baseline coverage, then move into a Study Plan before targeted Company Prep.