Core Entities
Board: (board_id, workspace_id, name, visibility=PUBLIC|PRIVATE, owner_id, created_at). A board represents a project. Column: (column_id, board_id, name, position, wip_limit). Columns represent workflow stages: Backlog, In Progress, Review, Done. WIP limit caps the number of tasks allowed in a column (Kanban principle). Task: (task_id, board_id, column_id, title, description, assignee_id, reporter_id, priority=LOW|MEDIUM|HIGH|CRITICAL, due_date, status, position, created_at). TaskLabel: many-to-many between tasks and labels. TaskComment: (comment_id, task_id, author_id, body, created_at). TaskAttachment: (attachment_id, task_id, file_url, filename, size_bytes, uploaded_by).
Task Workflow and State Machine
Task status follows the column: moving a task to a column changes its status. State transitions are column-based (not a fixed state machine) — columns are configurable. Enforce WIP limits on transition: before moving a task to a column: COUNT tasks in that column WHERE status != DONE. If count >= column.wip_limit: reject the move (or alert). Subtasks: a task can have sub-tasks (parent_task_id foreign key). Completion of a parent requires all subtasks to be complete. Task dependencies: (blocking_task_id, blocked_task_id) — a task cannot move to “In Progress” if a blocking task is not done. Blocked tasks are highlighted in the UI.
Assignment and Notifications
When a task is assigned to a user: create a notification for the assignee (type=TASK_ASSIGNED). When a task is mentioned (@user in a comment): create a notification for the mentioned user. When a task due date is within 24 hours: a scheduled job sends due date reminder notifications. Notification channels: in-app (notification bell, real-time via WebSocket), email digest (batched every hour for low-priority notifications), push notification (for mobile app). Subscribe/unsubscribe: users can watch any task to receive notifications for all activity on it. TaskWatcher table: (task_id, user_id) — all watchers receive notifications.
Activity Log
Record every action on a task: TaskActivity (activity_id, task_id, actor_id, action_type, old_value, new_value, created_at). Action types: CREATED, STATUS_CHANGED (old: In Progress, new: Review), ASSIGNED (new assignee), COMMENT_ADDED, DUE_DATE_CHANGED, PRIORITY_CHANGED. The activity log provides the full history of a task (visible as a timeline in the UI). Use it for: audit trails, debugging (“why did this task move back?”), and computing time-in-status metrics (how long tasks spend in each column).
Search and Filtering
Within a board: filter by assignee, label, priority, due date range. These are simple SQL queries on the tasks table. Full-text search on task title and description: use Elasticsearch for text search across all boards in a workspace. Index: task_id, board_id, workspace_id, title, description, assignee_id, labels. Query: text search + filters (board_id, assignee). Sorting: by due_date, priority, created_at, or manual drag-and-drop position. Position column: Decimal or float (LeetCode-style rebalancing — assign middle values between existing positions to avoid reindexing). When positions get too dense (float precision exhausted): rebalance all positions in the column.
Board Templates and Workflows
Templates: a Board can be created from a template (predefined set of Columns and task Labels). Store templates as a BoardTemplate entity with associated ColumnTemplate rows. On board creation from template: copy columns and labels. Custom fields: teams need custom data on tasks (story points, sprint, business value). CustomField (field_id, board_id, field_name, field_type=TEXT|NUMBER|DATE|SELECT). CustomFieldValue (task_id, field_id, value). This is a key-value EAV model — flexible but harder to query. For reporting, pre-aggregate custom field values into a separate analytics table.
Interview Tips
- The position field for drag-and-drop ordering is a common interview follow-up. Use lexicographic strings or floats to represent order without updating all rows. When gaps are exhausted, rebalance.
- Notifications are a separate concern — fan out to a notification service via Kafka events. Do not couple notification sending to the task update transaction.
- WIP limits and task dependencies are the “interesting” design details that separate a basic task tracker from a real project management tool. Mention them proactively.
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “How do you implement drag-and-drop ordering for tasks without updating all rows?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Drag-and-drop moves a task to a new position in a column. Naive approach: store position as an integer (1, 2, 3…). Moving task to position 2: update all tasks at position >= 2 to increment by 1. O(n) writes. Better approach: use a floating-point position. Insert between positions 2.0 and 3.0: assign 2.5. Between 2.0 and 2.5: assign 2.25. Only one row is updated. Problem: eventually positions are too close to represent as floats (precision exhausted). Solution: when the gap between adjacent positions = column.wip_limit: reject the move and return an error (‘Column In Progress has reached its WIP limit of 3’). The client highlights the column as over-limit in red. Policy options: hard limit (reject the move entirely) or soft limit (allow but visually flag the violation — teams sometimes need to exceed the limit temporarily). WIP limits are a Kanban principle that prevent context-switching overload and expose bottlenecks — if a column consistently hits its limit, the team needs to address the bottleneck upstream.”
}
},
{
“@type”: “Question”,
“name”: “How do you implement task dependencies (blocking/blocked) in a project management tool?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Task dependencies model ‘Task B cannot start until Task A is done’. Store in a TaskDependency table: (blocking_task_id, blocked_task_id). Circular dependency check: before inserting a dependency (A blocks B): verify B does not already (directly or transitively) block A — circular dependencies are invalid. Use DFS on the dependency graph. Moving a blocked task to ‘In Progress’: check if any of its blocking tasks are incomplete. If yes: prevent the move or show a warning. Display in UI: blocked tasks show a ‘blocked by’ badge. Dependency graph visualization: render the dependency graph as a directed acyclic graph. Critical path: the longest chain of dependencies determines the minimum project duration — useful for deadline estimation.”
}
},
{
“@type”: “Question”,
“name”: “How do you implement @mention notifications in task comments?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “When a user types a comment: parse the comment body for @username patterns (regex: /@[w]+/). For each matched username: look up user_id in the users table. If the user exists and has access to the board: create a notification (type=MENTIONED, task_id, comment_id, mentioned_user_id, actor_id). Deliver in real time via WebSocket if the user is online. Deliver via email if the user is offline or has email notifications enabled. Render the @mention as a styled link in the comment. Mention suggestions: as the user types @, fetch board members matching the typed prefix (autocomplete API). Access control: only mention users who are members of the workspace — prevent information leakage by mentioning external users.”
}
},
{
“@type”: “Question”,
“name”: “How would you scale a task management system to handle millions of tasks?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Shard tasks by workspace_id or board_id — most queries are workspace-scoped. Each workspace’s data lives on one shard, making joins and aggregations efficient. Read replicas for dashboards and reports (task counts, due date summaries, time-in-status analytics). Activity log: high-write table (every action creates a record). Partition by created_at month. Archive activity older than 6 months to cold storage. Search: use Elasticsearch for full-text task search across titles and descriptions. Sync from the database via a change data capture (CDC) pipeline (Debezium + Kafka). Attachments: store in S3 with presigned URLs; never serve files through the application server. Real-time updates: use WebSockets for collaborative editing — when one user moves a task, others see it immediately via a board-level Pub/Sub channel (Redis Pub/Sub or server-sent events).”
}
}
]
}
Asked at: Atlassian Interview Guide
Asked at: LinkedIn Interview Guide
Asked at: Snap Interview Guide
Asked at: Twitter/X Interview Guide