Chapter 10 · Article 48 of 55

Designing a Task Management System (Trello/Jira)

Design a task management system similar to Trello or Jira where teams can organize, track, and collaborate on work items across multiple projects. The system should allow users…

Article outline13 sections on this page

Problem Statement

Design a task management system similar to Trello or Jira where teams can organize, track, and collaborate on work items across multiple projects. The system should allow users to create tasks, assign them to team members, set priorities and deadlines, move tasks through workflow states, and maintain a complete audit trail of all changes.

Scenario: A mid-sized software company with 200+ employees across 15 teams needs a centralized task management platform. Each team manages multiple projects with varying workflows. Product managers create and prioritize tasks, developers pick up and work on them, and managers track progress through dashboards. The system must handle concurrent access from multiple users editing the same board, provide real-time updates when tasks change state, and support both Kanban-style continuous flow and Sprint-based iterations.

Users interact with the system through boards containing columns that represent workflow stages. Tasks (cards) move across columns as work progresses. Each task carries metadata including assignees, labels, due dates, attachments, and a comment thread for discussion.

The system must support different team workflows - some teams prefer Kanban-style continuous delivery while others follow Scrum with fixed-length sprints. Cross-team dependencies are common: a backend task might block a frontend task owned by a different team. The platform should provide visibility into these dependencies and alert stakeholders when blockers arise. Additionally, managers need reporting capabilities to track team velocity, identify bottlenecks, and forecast delivery timelines based on historical throughput.


Requirements

Functional Requirements

#RequirementDescription
FR-1Task CRUDCreate, read, update, and delete tasks with title, description, and metadata
FR-2AssignmentAssign tasks to one or more users; reassign as needed
FR-3Priority & Due DatesSet priority levels (Critical, High, Medium, Low) and due dates with reminders
FR-4Task StatesManage task lifecycle: TODO → IN_PROGRESS → IN_REVIEW → DONE, plus BLOCKED state
FR-5CommentsAdd threaded comments on tasks with @mentions
FR-6Labels & TagsCategorize tasks with colored labels and searchable tags
FR-7Boards & ProjectsOrganize tasks into boards within projects; each board has configurable columns
FR-8Search & FilterFull-text search across tasks; filter by assignee, label, priority, date range, state
FR-9NotificationsNotify users on assignment, mentions, state changes, approaching deadlines
FR-10Activity HistoryComplete audit log of all task changes with timestamp, actor, and diff
FR-11Task DependenciesDefine blocked-by and blocks relationships between tasks
FR-12AttachmentsUpload and attach files to tasks

Non-Functional Requirements

#RequirementTarget
NFR-1Concurrent AccessSupport 500+ simultaneous users editing boards without conflicts
NFR-2Real-time UpdatesBoard changes visible to all viewers within 500ms
NFR-3Data ConsistencyNo lost updates; optimistic locking with conflict resolution
NFR-4ScalabilityHandle 1M+ tasks across 10K+ projects
NFR-5Availability99.9% uptime; graceful degradation under load
NFR-6PerformanceTask creation < 200ms; search results < 500ms

Constraints & Assumptions

  • Users: Up to 10,000 registered users; 500 concurrent at peak
  • Projects: Each user belongs to multiple projects (avg 3-5)
  • Role-Based Access:
    • Admin - Full control: create/delete projects, manage members, configure workflows
    • Member - Create/edit/move tasks, comment, upload attachments
    • Viewer - Read-only access to boards and tasks
  • Storage: Tasks are text-heavy (avg 2KB each); attachments stored in object storage
  • Workflow: Each board can have custom column configurations (3-8 columns typical)
  • Retention: Archived tasks retained for 2 years; audit logs retained indefinitely
  • Integration: System exposes REST APIs; webhook support for external integrations

Key Entities & Relationships

EntityDescriptionKey Attributes
UserSystem actorid, name, email, role, avatar, preferences
ProjectTop-level containerid, name, description, owner, members[], settings
BoardVisual workspace within a projectid, name, projectId, columns[], type (Kanban/Scrum)
ColumnWorkflow stage on a boardid, name, boardId, position, wipLimit, stateMapping
TaskCore work itemid, title, description, state, priority, assignees[], labels[], dueDate, position
CommentDiscussion on a taskid, taskId, authorId, body, mentions[], createdAt
LabelCategorization tagid, name, color, projectId
AttachmentFile linked to a taskid, taskId, uploaderId, fileName, url, size
NotificationUser alertid, userId, type, payload, read, createdAt
ActivityLogAudit entryid, taskId, actorId, action, changes{}, timestamp
SprintTime-boxed iteration (optional)id, boardId, name, startDate, endDate, goal, tasks[]

Relationships:

  • Project 1:N Board; Board 1:N Column; Column 1:N Task
  • Task N:M User (assignees); Task N:M Label; Task 1:N Comment
  • Task 1:N Attachment; Task 1:N ActivityLog
  • Task N:M Task (dependencies); User 1:N Notification
  • Sprint 1:N Task (optional)

ASCII Class Diagram

┌─────────────────────────────────────────────────────────────────────────────────┐
│                          TASK MANAGEMENT SYSTEM                                  │
└─────────────────────────────────────────────────────────────────────────────────┘

┌──────────────┐       ┌──────────────────┐       ┌──────────────┐
│    User      │       │     Project      │       │    Label     │
├──────────────┤       ├──────────────────┤       ├──────────────┤
│ id           │       │ id               │       │ id           │
│ name         │◄──────┤ ownerId          │       │ name         │
│ email        │       │ name             │       │ color        │
│ role         │       │ description      │       │ projectId    │
│ avatar       │       │ members[]        │       └──────┬───────┘
└──────┬───────┘       └────────┬─────────┘              │
       │                        │ 1:N                    │ N:M
       │                        ▼                        │
       │               ┌──────────────────┐              │
       │               │      Board       │              │
       │               ├──────────────────┤              │
       │               │ id               │              │
       │               │ name             │              │
       │               │ projectId        │              │
       │               │ type             │              │
       │               └────────┬─────────┘              │
       │                        │ 1:N                    │
       │                        ▼                        │
       │               ┌──────────────────┐              │
       │               │     Column       │              │
       │               ├──────────────────┤              │
       │               │ id               │              │
       │               │ name             │              │
       │               │ position         │              │
       │               │ wipLimit         │              │
       │               │ stateMapping     │              │
       │               └────────┬─────────┘              │
       │                        │ 1:N                    │
       │    N:M                 ▼                        │
       │◄──────────────┌──────────────────┐──────────────┘
       │  (assignees)  │      Task        │
       │               ├──────────────────┤
       │               │ id               │    ┌──────────────────┐
       │               │ title            │    │   Attachment     │
       │               │ description      │    ├──────────────────┤
       │               │ state            │───▶│ id, taskId       │
       │               │ priority         │    │ fileName, url    │
       │               │ dueDate          │    └──────────────────┘
       │               │ position         │
       │               │ dependencies[]   │──┐ (self-referencing N:M)
       │               └───┬─────────┬────┘◄─┘
       │                   │         │
       │              1:N  │         │ 1:N
       │                   ▼         ▼
       │  ┌────────────────┐   ┌──────────────────┐
       │  │    Comment     │   │  ActivityLog     │
       │  ├────────────────┤   ├──────────────────┤
       │  │ id             │   │ id               │
       └─▶│ authorId       │   │ actorId          │
          │ body           │   │ action           │
          │ mentions[]     │   │ changes{}        │
          │ createdAt      │   │ timestamp        │
          └────────────────┘   └──────────────────┘

┌─────────────────────────────────────────────┐
│         TASK STATE MACHINE                  │
├─────────────────────────────────────────────┤
│                                             │
│   ┌──────┐  assign   ┌─────────────┐       │
│   │ TODO │──────────▶│ IN_PROGRESS │       │
│   └──┬───┘           └──┬──────┬───┘       │
│      │                  │      │            │
│      │ block            │      │ submit     │
│      ▼                  │      ▼            │
│   ┌─────────┐  resolve │  ┌───────────┐    │
│   │ BLOCKED │◄─────────┘  │ IN_REVIEW │    │
│   └────┬────┘             └─────┬─────┘    │
│        │ unblock                │ approve   │
│        ▼                        ▼           │
│   ┌─────────────┐         ┌────────┐       │
│   │ IN_PROGRESS │         │  DONE  │       │
│   └─────────────┘         └────┬───┘       │
│                                 │ reopen    │
│                                 ▼           │
│                            ┌──────┐         │
│                            │ TODO │         │
│                            └──────┘         │
└─────────────────────────────────────────────┘

Design Decisions & Trade-offs

1. Task State Management: Enum vs State Pattern

ApproachProsCons
Enum + transition tableSimple, easy to persist, fast validationHard to extend with per-state behavior
State Pattern (chosen)Encapsulates transition logic, enforces valid moves, extensibleMore classes, slight overhead

Decision: Use the State pattern. Each state object defines allowed transitions and side effects (e.g., moving to DONE triggers notification). The state enum is still stored in the database for querying, but runtime behavior is delegated to state objects. This cleanly separates "what transitions are valid" from "what happens on transition."

2. Notification Strategy: Push vs Pull, Real-time vs Batch

Decision: Hybrid approach. Use WebSocket push for real-time board updates (task moved, new comment) and batch digest emails for non-urgent notifications (daily summary, weekly report). Users configure preferences per notification type. Critical notifications (assignment, blocking) are always pushed immediately.

Trade-off: Real-time push increases server resource usage but dramatically improves UX for collaborative boards. Batching reduces notification fatigue for less time-sensitive events.

3. Search Implementation: In-memory vs Indexed

Decision: Use an inverted index (Elasticsearch/Lucene-based) for full-text search. In-memory filtering works for small datasets but fails at scale. The search index is updated asynchronously via events when tasks change.

Trade-off: Adds infrastructure complexity and slight indexing delay (eventual consistency for search), but provides sub-second search across millions of tasks with faceted filtering.

4. Access Control: RBAC vs ACL

Decision: RBAC at the project level (Admin/Member/Viewer) with optional per-board overrides. Pure ACL is too granular for most teams and creates management overhead. RBAC covers 90% of use cases; board-level overrides handle the remaining edge cases (e.g., a private board within a project).

Trade-off: Less granular than full ACL but significantly simpler to reason about, audit, and manage. If per-task permissions are needed later, layer ACL on top.

5. Audit Log Design: Event Sourcing vs Simple Log

Decision: Simple append-only activity log with structured change diffs. Full event sourcing (rebuilding state from events) is overkill for a task system where the current state is always authoritative.

Trade-off: We lose the ability to replay history to reconstruct past states, but gain simplicity in reads and avoid the complexity of event replay, snapshots, and eventual consistency. The activity log stores before/after values for each field change, which is sufficient for audit and undo.


Design Patterns Used

Observer Pattern - Notifications

When a task changes state, the TaskService publishes a TaskEvent. Registered observers (EmailNotifier, WebSocketNotifier, SlackNotifier) react independently. This decouples task logic from notification delivery. New notification channels can be added without modifying the task service. The event bus supports both synchronous (in-process) and asynchronous (message queue) delivery depending on latency requirements.

State Pattern - Task Lifecycle

Each task state (TodoState, InProgressState, BlockedState, InReviewState, DoneState) implements a TaskState interface with methods like moveToNext(), block(), resolve(). Invalid transitions throw exceptions rather than silently failing. Each state object also defines which UI actions are available, enabling the frontend to dynamically show/hide buttons based on current state without duplicating transition logic.

Command Pattern - Task Operations (Undo Support)

Each task mutation (ChangeTitle, AssignUser, MoveColumn, UpdatePriority) is encapsulated as a Command object with execute() and undo() methods. Commands are stored in a stack per user session, enabling undo/redo. Commands also feed the audit log, creating a single source of truth for "what happened." Composite commands handle multi-step operations (e.g., "move to done and unassign") as atomic units.

Strategy Pattern - Notification Channels

The notification system uses interchangeable strategies (EmailStrategy, PushStrategy, SlackStrategy, InAppStrategy) selected based on user preferences and notification type. Adding a new channel requires only implementing the strategy interface and registering it in the configuration. The strategy selector considers user timezone, quiet hours, and urgency level when choosing delivery method.

Factory Pattern - Task Creation

A TaskFactory creates tasks with sensible defaults (state=TODO, priority=Medium, position=last) and validates required fields. Different factories can produce different task types (Bug, Story, Epic, Spike) with appropriate default labels, fields, and workflow configurations. The factory also handles ID generation, timestamp assignment, and initial event emission.


Edge Cases

1. Circular Task Dependencies

Problem: Task A blocks Task B, Task B blocks Task C, Task C blocks Task A - creating a deadlock where no task can proceed. Solution: Run cycle detection (DFS/topological sort) when adding a dependency. Reject the operation if it would create a cycle. Return a clear error message showing the dependency chain. Validate transitively - if A→B→C exists, adding C→A must be caught even though C doesn't directly reference A in the immediate request.

2. Concurrent State Changes

Problem: Two users simultaneously move the same task to different columns, or one user edits the description while another changes the assignee. Solution: Optimistic locking with version numbers. Each task carries a version field incremented on every write. Updates include the expected version; if it doesn't match, the operation fails with a conflict error. The client can then refresh and retry. For non-conflicting field changes (description vs assignee), consider field-level versioning to reduce false conflicts.

3. Bulk Operations

Problem: Moving 50 tasks between columns, bulk-assigning, or importing from CSV must be atomic and performant without flooding the notification system. Solution: Wrap bulk operations in a transaction. Use batch database operations. Emit a single aggregate event rather than 50 individual notifications. Show progress for large operations. Implement a "bulk mode" flag that notification observers check to aggregate alerts into a single summary.

4. Task Archival

Problem: Completed tasks accumulate over months and slow down board queries and search indexing. Solution: Auto-archive tasks 30 days after completion. Archived tasks are excluded from default queries but remain searchable with an explicit filter. Separate archive storage with lazy loading. Provide a "restore from archive" action that moves tasks back to the board's first column.

5. Deleted User's Tasks

Problem: A user leaves the organization; their assigned tasks become orphaned, and their comments/history must be preserved for audit. Solution: On user deactivation, reassign their tasks to the project admin or mark as "Unassigned" with a visual indicator. Preserve the user's name in comments and history (soft delete - never hard-delete users with task associations). Display deactivated users with a visual badge in the UI to distinguish from active members.

6. Overdue Task Handling

Problem: Tasks past their due date need visibility without manual checking; stakeholders need proactive alerts. Solution: A scheduled job runs daily (and hourly for critical-priority tasks), marking overdue tasks with a flag and triggering notifications to assignees and project admins. Overdue tasks surface in dashboard widgets with escalating urgency indicators. Configurable escalation rules can auto-reassign or auto-elevate priority after N days overdue.


Extensibility Points

Workflow Automation

Allow users to define rules: "When a task moves to DONE, automatically notify the reporter" or "When all subtasks are complete, move parent to IN_REVIEW." Implement as a rule engine evaluating conditions on task events.

External Integrations

  • Slack/Teams: Post task updates to channels; create tasks from messages
  • Email: Create tasks by forwarding emails; receive digest notifications
  • GitHub/GitLab: Link commits/PRs to tasks; auto-transition on merge
  • Webhooks: Generic event delivery for custom integrations

Custom Fields

Allow projects to define additional typed fields (text, number, date, dropdown) on tasks. Store as a flexible JSON column or EAV (Entity-Attribute-Value) pattern. Custom fields are searchable and filterable.

Time Tracking

Add time logging per task with start/stop timers and manual entry. Aggregate time at project/sprint level for reporting. Integrate with invoicing systems for client-facing projects.

Recurring Tasks

Define templates that auto-generate tasks on a schedule (daily standup notes, weekly reviews). Use cron expressions for flexibility. Each generated task links back to its template.

Kanban vs Scrum Views

  • Kanban: Continuous flow, WIP limits per column, cumulative flow diagrams
  • Scrum: Sprint planning, backlog grooming, burndown charts, velocity tracking
  • Both views operate on the same underlying data; the board type determines which features and metrics are available.

Interview Follow-ups

Q1: How would you handle real-time collaboration when multiple users are editing the same task simultaneously?

Answer: Use Operational Transformation (OT) or CRDTs for the task description field (rich text). For discrete fields (state, assignee, priority), use last-writer-wins with optimistic locking. Each client maintains a WebSocket connection to receive live updates. When a conflict is detected on a discrete field, show the user a conflict resolution dialog with both values. For the description, merge changes automatically at the character level, similar to Google Docs.

Q2: How would you design the system to support 10,000 boards with 100,000 tasks each?

Answer: Partition data by project/organization. Use database sharding on projectId. Cache hot boards (recently active) in Redis with TTL. Paginate task lists and use cursor-based pagination for infinite scroll. Materialize board views (task counts per column, overdue counts) as cached aggregates updated via events. Use read replicas for dashboard queries. Archive inactive boards to cold storage.

Q3: How would you implement an undo feature for task operations?

Answer: The Command pattern stores each operation with its inverse. Maintain a per-user undo stack (last 20 operations, TTL 30 minutes). Each command captures the before-state needed to reverse it. Undo is itself a command (for redo). For operations that trigger side effects (notifications sent), undo reverses the data change but cannot recall sent notifications - document this limitation. Multi-step undo requires careful ordering; use a transaction log approach.

Hint-only Follow-ups

Q4: How would you implement task priority auto-adjustment based on dependencies and deadlines?

  • Hint: Think about topological ordering of the dependency graph combined with deadline propagation. Consider how a blocked task's priority should inherit from its blocker's urgency.

Q5: How would you design the notification system to avoid overwhelming users during bulk imports?

  • Hint: Consider a notification aggregation window (debounce). Think about distinguishing between user-initiated actions and system/bulk operations at the event level.

Counter Questions to Ask Interviewer

Before diving into design, clarify scope with these questions:

  1. Scale: How many users and tasks are we designing for? Is this a startup tool or enterprise-grade?
  2. Workflow complexity: Do we need custom workflows per project, or is a fixed set of states sufficient?
  3. Real-time requirements: Do users need to see live cursor positions and typing (Google Docs-style), or just state change notifications?
  4. Multi-tenancy: Is this a SaaS product serving multiple organizations, or a single-tenant deployment?
  5. Offline support: Should users be able to work offline and sync later?
  6. Reporting: Are analytics dashboards (velocity, burndown, cycle time) in scope?
  7. Hierarchy: Do we need task hierarchies (Epic → Story → Subtask), or is it flat?
  8. Access granularity: Is project-level RBAC sufficient, or do we need per-task permissions?
  9. Compliance: Are there audit/compliance requirements (SOC2, GDPR) affecting data retention?
  10. Integration priority: Which external integrations are must-haves for v1?

References

  1. Design Patterns: Elements of Reusable Object-Oriented Software - Gamma et al. (Gang of Four) - State, Observer, Command, Strategy, Factory patterns
  2. Domain-Driven Design - Eric Evans - Bounded contexts, aggregates, domain events
  3. Designing Data-Intensive Applications - Martin Kleppmann - Event sourcing, consistency models, partitioning strategies
  4. Trello Architecture - Fog Creek Engineering Blog - Real-time board synchronization, WebSocket architecture
  5. Jira Data Model - Atlassian Developer Documentation - Issue types, workflows, custom fields, permission schemes
  6. CRDTs for Real-time Collaboration - Shapiro et al. - Conflict-free replicated data types for concurrent editing
  7. RBAC (Role-Based Access Control) - NIST Standard - Role hierarchies, permission assignment, separation of duties
  8. The Reactive Manifesto - Responsive, resilient, elastic, message-driven systems
  9. Building Microservices - Sam Newman - Service decomposition, event-driven architecture, saga pattern for distributed transactions
  10. WebSocket Protocol (RFC 6455) - Full-duplex communication for real-time updates

This problem tests your ability to model complex domain entities, handle concurrent access patterns, design extensible state machines, and balance simplicity against future requirements. Focus on clearly communicating trade-offs rather than arriving at a single "correct" answer.