. Design Jira-like Project Management Tool Frontend System Design Interview Guide
Design a production-ready project management tool like Jira with Kanban boards, sprints, epics, real-time collaboration, and complex workflow automation.
Backend as Black Box: Assume APIs exist for issues, boards, workflows, and WebSocket events. Focus on frontend architecture, state modeling, performance, and synchronization.
Key Challenges
- Workflow correctness with guarded transitions
- 10,000+ issues without UI jank
- Real-time multi-user updates
- 60fps drag & drop
- JQL-like filtering at scale
When teams manage projects, they expect smooth drag-and-drop, real-time collaboration, and efficient handling of 10,000+ issues—even with complex workflows. This solution designs a Jira-like project management tool that handles Kanban boards, workflow automation, real-time collaboration, and complex state management while maintaining 60fps drag interactions. The key insight: a good project management tool normalizes state for scale, models workflows explicitly, and handles conflicts gracefully.
HLD interview focus: Requirements, architecture, tradeoffs, data flow, and scaling decisions. Any implementation snippets shown are optional unless explicitly asked.
I'll start by defining what makes a great project management experience—what questions does a user need answered as they manage issues? Then I'll design the architecture that handles Kanban boards, workflow automation, real-time collaboration, and complex state management. Finally, I'll design clear boundaries between server state and UI state.
Why this approach?
Most candidates build a "board with cards." Strong candidates build a "project management system"—a system that handles workflow correctness, scales to 10,000+ issues, and maintains 60fps drag interactions. The difference is thinking about state normalization, workflow modeling, and conflict resolution, not just drag-and-drop.
Think of this like building Jira or Linear. You don't just show cards—you handle workflow transitions, real-time collaboration, complex filtering, and scale. Same principles apply here.
Before designing anything, let's define what success looks like. When teams manage projects, they need smooth drag-and-drop, real-time collaboration, and efficient issue management.
Requirements Exploration Questions
DiscoveryAsk your interviewer these questions to refine requirements.
What workflow model is required?
- Kanban only or Kanban + Scrum boards?
- Project-level workflow customization?
- Required transition guards (assignee, checklist, approvals)?
What scale should we design for?
- Typical and peak issue counts per board
- Concurrent editors per board/project
- Number of boards a user actively monitors
What collaboration behavior matters most?
- Live card moves and field edits
- Conflict handling for simultaneous edits
- Notification expectations for watched issues
What access model applies?
- Project roles and field-level permissions
- External guests vs internal users
- Audit/history requirements
Functional Requirements
Must HaveMVP (Core Features - What I'd Design First):
- Kanban board with drag-and-drop between workflow columns
- Issue CRUD with assignee, priority, labels, due date
- Project-scoped filters and basic search
- Real-time board updates for active collaborators
- Keyboard support for issue navigation and quick actions
- Clear loading/empty/error states
Advanced Features (Add If Time Permits):
- Sprint planning + backlog grooming workflows
- Timeline/Gantt + dependency visualization
- Automation rules (status transitions, notifications)
- Offline queue with conflict-safe reconciliation
Non-Functional Requirements
Quality BarPerformance Targets:
- Drag interaction at 60fps for typical board sizes
- Local card move feedback < 100ms
- Board refresh/sync visibility < 500ms
- Initial board load < 2s for common projects
Scalability Goals:
- Handle 10K+ issues per project with virtualization
- Support 100+ active users on the same board
- Sustain high update throughput during planning sessions
Reliability & Consistency:
- Idempotent write operations and retry-safe mutations
- Deterministic ordering/reconciliation after reconnect
- Conflict-safe merge strategy for concurrent edits
Accessibility Requirements:
- WCAG 2.1 AA keyboard and screen-reader support
- Keyboard-only drag/drop fallback interactions
- Clear focus states and ARIA announcements for moves
Security & Compliance:
- Role-based authz on all write operations
- Input validation + XSS/CSRF protections
- Audit-friendly change history for issue transitions
Observability:
- Track p95 board load, mutation latency, and error rate
- Monitor queue retry depth and sync conflict frequency
- Alert on sustained drag-drop failure or websocket disconnect spikes
High-Level Architecture
┌───────────────────────────────┐
│ UI LAYER │
│ Board / Issue Panel / Filters │
└───────────────┬───────────────┘
│
▼
┌───────────────────────────────┐
│ STATE LAYER │
│ Normalized Store │
│ Workflow Engine │
│ Derived Selectors │
└───────────────┬───────────────┘
│
▼
┌───────────────────────────────┐
│ SYNC LAYER │
│ WebSocket + Offline Queue │
│ Conflict Resolver │
└───────────────┬───────────────┘
│
▼
┌───────────────────────────────┐
│ SERVER (Black Box) │
│ REST + WebSocket APIs │
└───────────────────────────────┘Design Principles
- UI reads from state only
- State is normalized
- Server is authoritative
- Optimistic updates for responsiveness
Tradeoffs & Comparisons
- CSR vs SSR/ISR: Rendering Strategies
Entity and interface contract shape with cache/reconciliation model for a frontend system design interview (backend treated as a black box).
1) Component prop interfaces (boundaries)
Separate board rendering, issue details, and workflow actions via explicit contracts.
BoardShellProps: project context + board-level controlsBoardColumnProps: ordered issue ids + drag/drop callbacksIssueCardProps: summary payload + quick actionsIssueDetailPanelProps: editable issue model + save/transition handlers
2) Hook interfaces (consumption contracts)
Hooks expose board data and mutation contracts while hiding transport details.
Normalized State Model
Instead of nested objects:
Column → Issues → Issue DetailsUse lookup tables:
issues: { issueId → Issue }
columns: { columnId → Column }
issuesByColumn: { columnId → issueId[] }
columnOrder: [columnId]Benefits:
- O(1) lookups
- Stable ordering
- Minimal rerenders
- Easy offline serialization
Workflow as State Machine
To Do ──Start──▶ In Progress ──Review──▶ Done
▲ │
└────────────── Reopen ◀─────────────┘Transition Flow:
- User action
- Client validation
- Guard execution
- Optimistic update
- Server confirm or rollback
Client cache shape (recommended)
entitiesById: Record<ID, Entity>orderedIds: ID[]for rendering orderpageInfo/cursor metadata for pagination or range loading
Deep dive: Data Normalization
Consistency & reconciliation rules
- Make writes idempotent where retries are possible.
- Apply realtime updates with version/event ordering checks.
- Prefer server-authoritative reconciliation after optimistic mutations.
Tradeoffs & Comparisons
- Normalized vs Denormalized: Data Normalization
Component Boundaries
StructuredBoardShell
Owns filters, swimlane mode, and board-level orchestration.
BoardColumn
Renders ordered issue cards and emits drag/drop move intents.
IssueCard
Displays summary metadata and lightweight action callbacks.
IssueDetailPanel
Handles issue editing, transitions, and comment interactions.
export interface BoardShellProps {
projectId: string;
activeSprintId?: string;
onOpenIssue: (issueId: string) => void;
}
export interface BoardColumnProps {
columnId: string;
issueIds: string[];
onMoveIssue: (input: { issueId: string; toColumnId: string; index: number }) => void;
}
export interface IssueCardProps {
issue: IssueSummary;
onOpen: () => void;
onAssign: (assigneeId: string | null) => void;
}
export interface IssueDetailPanelProps {
issueId: string;
onSave: (patch: Partial<IssueDetail>) => Promise<void>;
onTransition: (toStateId: string) => Promise<void>;
}export interface UseBoardDataResult {
columns: BoardColumnModel[];
issuesById: Record<string, IssueSummary>;
isLoading: boolean;
}
export interface UseIssueMutationsResult {
moveIssue: (input: { issueId: string; toColumnId: string; index: number }) => Promise<void>;
updateIssue: (issueId: string, patch: Partial<IssueDetail>) => Promise<void>;
pendingCount: number;
}
export function useBoardData(_projectId: string): UseBoardDataResult {
throw new Error('Contract-only snippet');
}
export function useIssueMutations(_projectId: string): UseIssueMutationsResult {
throw new Error('Contract-only snippet');
}React interfaces & integration patterns (props, hooks, callbacks).
This section covers API contracts and React consumption patterns.
API contracts (Backend as black box)
1) Board + issue list reads
GET /api/boards/:boardId
=> {
board: { id: string; name: string; columnOrder: string[] },
columns: Array<{ id: string; name: string; wipLimit?: number }>
}
GET /api/boards/:boardId/issues?cursor=<opaque|null>&limit=50&sort=priority|updatedAt&assignee=&label=&q=
=> {
items: IssueSummary[],
pageInfo: { nextCursor: string | null, hasMore: boolean }
}2) Issue CRUD
POST /api/issues
body: { boardId: string, title: string, description?: string, columnId: string, idempotencyKey: string }
=> { issue: IssueDetail }
PATCH /api/issues/:id
body: { patch: Partial<IssueDetail>, version: number, idempotencyKey: string }
=> { issue: IssueDetail, version: number }
DELETE /api/issues/:id
body: { version: number, idempotencyKey: string }
=> { ok: true }3) Workflow transition + drag/drop move
POST /api/issues/:id/transition
body: { toStateId: string, version: number, idempotencyKey: string }
=> { issue: IssueDetail, version: number }
PUT /api/issues/:id/move
body: { targetColumnId: string, position: number, version: number, idempotencyKey: string }
=> { issueId: string, columnId: string, position: number, version: number }4) Comments + activity
GET /api/issues/:id/comments?cursor=<opaque|null>&limit=20
=> { items: Comment[], pageInfo: { nextCursor: string | null, hasMore: boolean } }
POST /api/issues/:id/comments
body: { content: string, idempotencyKey: string }
=> { comment: Comment }5) Search / filters / saved views
GET /api/search/issues?boardId=&q=&assignee=&label=&state=&cursor=<opaque|null>&limit=50
=> { items: IssueSummary[], pageInfo: { nextCursor: string | null, hasMore: boolean } }
POST /api/views
body: { boardId: string, name: string, filters: IssueFilter }
=> { view: SavedView }6) Realtime channel (board scoped)
WS /realtime/boards/:boardId
// examples
{ type: 'ISSUE_CREATED', eventId: string, issue: IssueSummary, version: number }
{ type: 'ISSUE_UPDATED', eventId: string, issueId: string, patch: Partial<IssueDetail>, version: number }
{ type: 'ISSUE_MOVED', eventId: string, issueId: string, from: string, to: string, position: number, version: number }
{ type: 'ISSUE_DELETED', eventId: string, issueId: string, version: number }
{ type: 'COMMENT_ADDED', eventId: string, issueId: string, comment: Comment, version: number }
{ type: 'PRESENCE_UPDATED', eventId: string, userId: string, boardId: string, state: 'active' | 'idle' }Type definitions used in contracts
interface IssueSummary {
id: string;
boardId: string;
columnId: string;
title: string;
assigneeId?: string | null;
priority: 'low' | 'medium' | 'high' | 'critical';
labels: string[];
updatedAt: string;
version: number;
}
interface IssueDetail extends IssueSummary {
description?: string;
stateId: string;
reporterId: string;
dueDate?: string | null;
estimatePoints?: number | null;
createdAt: string;
}
interface Comment {
id: string;
issueId: string;
authorId: string;
content: string;
createdAt: string;
updatedAt?: string;
}
interface IssueFilter {
q?: string;
assigneeIds?: string[];
labels?: string[];
stateIds?: string[];
priorities?: Array<'low' | 'medium' | 'high' | 'critical'>;
}
interface SavedView {
id: string;
boardId: string;
name: string;
filters: IssueFilter;
}Consistency rules
- All write APIs carry
idempotencyKeyandversion. - Server is authoritative on conflict (409 returns latest entity/version).
- Client ignores stale realtime events older than cached
version.
3) Integration patterns (React wiring)
- Optimistic DnD: move card locally first, then reconcile with authoritative state.
- Patch-by-id updates: mutate only touched issue entities to avoid full-board rerenders.
- Permission-aware actions: disable restricted transitions based on workflow role rules.
- Reconnect recovery: replay queued issue mutations with idempotency keys.
Integration Patterns
StructuredOptimistic DnD
Apply local card move immediately, rollback or patch after server ack.
Entity-level patching
Update only affected issue entities and column order arrays.
Workflow guards
Enforce allowed transitions at interaction boundaries.
Offline-safe queue
Queue mutations and replay deterministically on reconnect.
Virtualization Strategy
Above viewport (not rendered)
Buffer
VISIBLE viewport
Buffer
Below viewport (not rendered)Effect: DOM size decoupled from dataset size
Optimistic Sync Flow
User Action → Optimistic Update → Server → Confirm or RollbackIndexed Filtering
byAssignee: { userId → Set(issueIds) }
byStatus: { status → Set(issueIds) }Intersect indexed sets, then apply remaining filters.
Performance Targets
| Metric | Target |
|---|---|
| Drag latency | <16ms |
| Filter response | <100ms |
| Sync latency | <500ms |
| Memory (10K issues) | <100MB |
Why This Works
- Workflow modeled as state machine for correctness
- Normalized state ensures O(1) access
- Virtualization keeps UI fast at scale
- Optimistic updates preserve responsiveness
- Server authority ensures consistency
Key Takeaways
- Model workflows as state machines
- Normalize state
- Virtualize large lists
- Use optimistic updates with rollback
- Index frequently filtered fields
Key Takeaways
- ✓State machines enforce workflow correctness
- ✓Normalized state enables O(1) access patterns
- ✓Virtualization maintains performance at 10K+ issues
- ✓Optimistic updates improve perceived responsiveness
- ✓Indexed filtering ensures scalable query performance