Back to System Design
System Design
hard
mid

How would you design the three column functionality of a Jira board (Open, In Progress, Done)?

Model columns and cards as normalized state, render columns mapping over ordered card-id lists, implement drag-and-drop with optimistic reordering, persist moves to the server, and handle real-time multi-user sync, virtualization for big boards, and conflict resolution.

7 min read·~35 min to think through

A Jira-style 3-column board is a drag-and-drop + ordered-lists + sync problem. The columns are easy; ordering, DnD, persistence, and collaboration are where the design lives.

Data model — normalized

js
columns: { open: { id, title, cardIds: [...] }, inProgress: {...}, done: {...} }
cards:   { 'c1': { id, title, assignee, ... }, ... }
columnOrder: ['open', 'inProgress', 'done']

Cards stored by id; each column holds an ordered array of card ids. Normalization makes moves O(1) and avoids deep nesting.

Rendering

  • Map columnOrder<Column>; each column maps its cardIds<Card>.
  • Memoize <Card> (keyed by id) so moving one card doesn't re-render the whole board.

Drag and drop

  • Use a library — @dnd-kit (modern, accessible) or react-beautiful-dnd/pragmatic-drag-and-drop. DnD with keyboard accessibility and auto-scroll is hard to hand-roll.
  • On drop: remove the card id from the source column's array, insert at the target index in the destination array. Two cases: reorder within a column, move across columns.
  • Ordering at scale: for big boards, instead of reindexing every card, use fractional/lexicographic rank keys (a card gets a rank between its neighbors) so a move updates one card's rank, not the whole list.

Optimistic updates + persistence

  • Update local state immediately on drop (instant feel), then PATCH the move to the server.
  • On failure → roll back and show an error.
  • Server is the source of truth for order; reconcile with its response.

Real-time collaboration

  • Multiple users move cards simultaneously → WebSocket broadcasting card-move events.
  • Apply remote moves as targeted patches; don't yank a card out from under a user mid-drag.
  • Conflict: two users move the same card — last-write-wins with a timestamp is usually acceptable for a board; surface "card moved by X" if needed. Rank keys make concurrent moves merge cleanly most of the time.
  • Resync on reconnect (refetch board state).

Other concerns

  • States: loading skeleton, empty column, error.
  • Virtualization — a column with hundreds of cards should virtualize.
  • WIP limits — block/warn when "In Progress" exceeds its limit.
  • Accessibility — keyboard DnD, ARIA, focus management.
  • Filtering/search — derived views over the same normalized state.

The framing

"Normalized state — cards by id, columns holding ordered id arrays. DnD via a library, with optimistic reorder on drop and a PATCH to persist; rank keys so a move touches one card not the list. Real-time sync over WebSocket applying remote moves as targeted patches, last-write-wins for conflicts. Plus virtualization, WIP limits, and keyboard accessibility."

Follow-up questions

  • Why use fractional rank keys instead of array indices for ordering?
  • How do you handle two users moving the same card at once?
  • How do optimistic updates work for a drag-and-drop move?
  • How would you keep a 500-card column performant?

Common mistakes

  • Deeply nested state (cards inside columns) making moves and updates painful.
  • Reindexing every card on each move instead of using rank keys.
  • No optimistic update — laggy drag that waits on the network.
  • Ignoring real-time conflicts and reconnection resync.
  • Hand-rolling DnD and missing keyboard accessibility.

Performance considerations

  • Normalized state + memoized cards bound the re-render blast radius. Rank keys make a move an O(1) single-card update. Virtualize large columns. Batch real-time patches to avoid render storms.

Edge cases

  • Concurrent moves of the same card by different users.
  • Dropping a card back in its original position.
  • A move that fails server-side after the optimistic update.
  • Empty columns as drop targets.
  • Very large columns needing virtualization.

Real-world examples

  • Jira, Trello, Linear board views.
  • @dnd-kit + a normalized store + WebSocket move events.

Senior engineer discussion

Seniors lead with normalized state and ordered id arrays, raise fractional rank keys so moves don't reindex the list, and design the optimistic-update + persistence + rollback loop. They treat real-time collaboration (targeted patches, LWW conflict handling, reconnect resync) and accessibility/virtualization as core, not extras.

Related questions