Core Entities
Workspace: workspace_id, name, owner_id, plan (FREE, PRO, ENTERPRISE), created_at. Board: board_id, workspace_id, name, description, visibility (PUBLIC, PRIVATE, WORKSPACE), created_by. Column: column_id, board_id, name, order_index, wip_limit (nullable, max tasks in this column). Task: task_id, board_id, column_id, title, description, assignee_id, reporter_id, priority (LOW, MEDIUM, HIGH, CRITICAL), status, due_date, story_points, labels (JSON array), order_index (within column). Comment: comment_id, task_id, author_id, body, created_at. Attachment: attachment_id, task_id, uploader_id, file_name, s3_key, file_size.
Task and Column Design
from enum import Enum
from dataclasses import dataclass, field
from typing import List, Optional
from datetime import datetime
class Priority(Enum):
LOW = 1; MEDIUM = 2; HIGH = 3; CRITICAL = 4
@dataclass
class Task:
task_id: int
title: str
column_id: int
board_id: int
assignee_id: Optional[int] = None
priority: Priority = Priority.MEDIUM
due_date: Optional[datetime] = None
labels: List[str] = field(default_factory=list)
order_index: float = 0.0 # fractional indexing for reordering
class TaskService:
def move_task(self, task_id: int, target_column_id: int,
position: int, user_id: int) -> Task:
task = self.repo.get_task(task_id)
target_col = self.repo.get_column(target_column_id)
# Check WIP limit
if target_col.wip_limit is not None:
current_count = self.repo.count_tasks_in_column(target_column_id)
if current_count >= target_col.wip_limit and task.column_id != target_column_id:
raise WIPLimitExceededError(target_col.wip_limit)
# Compute fractional order_index for the target position
tasks_in_col = self.repo.get_tasks_in_column(target_column_id, sorted=True)
new_order = self._compute_order(tasks_in_col, position)
task.column_id = target_column_id
task.order_index = new_order
self.repo.save(task)
# Publish event for activity log and notifications
self.events.publish(TaskMovedEvent(task_id, target_column_id, user_id))
return task
def _compute_order(self, tasks, position):
if not tasks: return 1000.0
if position == 0: return tasks[0].order_index / 2
if position >= len(tasks): return tasks[-1].order_index + 1000.0
return (tasks[position-1].order_index + tasks[position].order_index) / 2
Fractional Indexing for Ordering
Ordering tasks within a column by integer index requires updating all subsequent tasks on every reorder (expensive). Fractional indexing avoids this: insert a task between positions with order_index = (prev + next) / 2. Ordering by order_index float gives the correct sequence without updating siblings. Problem: after many insertions between the same two tasks, the gap shrinks toward machine epsilon. Solution: periodically rebalance — rewrite all order_index values in a column as evenly spaced integers (1000, 2000, 3000…). Rebalance triggers when any gap < threshold (e.g., 0.001). Rebalancing is rare (a few times per million operations) and can run as a background task.
Workflow Automation (Like Jira Automation)
Users define rules: trigger → condition → action. Trigger: task moved to column, task assigned, due date approaching. Condition: priority == HIGH, label contains “bug”. Action: assign to a user, add a label, send a notification, create a sub-task. Implementation: store rules as JSON: {trigger: “TASK_MOVED”, trigger_params: {column_name: “Done”}, conditions: [{field: “priority”, op: “eq”, value: “HIGH”}], actions: [{type: “ASSIGN”, value: “manager_id”}]}. When a task event fires: load all rules for the board, evaluate conditions against the task, execute matching actions. Rules engine: a simple interpreter — for each condition, read the field from the task object and evaluate the operator. Complex conditions: AND/OR grouping stored as a condition tree (JSON).
Activity Log and Notifications
Every task mutation generates an activity_log entry: (task_id, actor_id, action_type, old_value, new_value, created_at). Displayed as the task history timeline. For notifications: a task has subscribers (assignee, reporter, commenters, explicit watchers). On events (new comment, task moved to In Review, approaching due date): send a notification to all subscribers. Notification batching: users can configure digest mode (one email per hour summarizing all changes). Store pending notifications; a job runs hourly, groups by user, sends one email. @mentions in comments: parse comment body for @username patterns, extract mentioned users, add them as subscribers, send them an immediate notification. Avoid notification spam: deduplicate — if a task moves three times in one hour, send one notification covering all moves, not three.
Search and Filters
Task search: full-text on title, description, and comments via Elasticsearch. Updated by a CDC pipeline from PostgreSQL. Filter by: assignee, reporter, label, priority, column, due date range, creation date. The combination of filters is an Elasticsearch bool query with term/range filters. Board-level search is common; workspace-level search (across all boards) requires appropriate permission checks. Saved filters: users save frequently used filter combinations. Store as JSON; apply at query time. “My open tasks” = assignee=me AND column != Done — the most common saved filter.
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “What is fractional indexing and why is it better than integer indexing for task ordering?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Integer indexing (task positions are 1, 2, 3, …): inserting between position 2 and 3 requires updating all subsequent tasks to 3, 4, 5… This is an O(n) update per insertion. For a board with 1000 tasks, dragging one card to a new position requires updating 999 rows. Fractional indexing: positions are floating-point numbers (1000.0, 2000.0, 3000.0). Insert between 1000 and 2000: new position = 1500. Insert between 1000 and 1500: 1250. Only one row is updated (the moved task). O(1) per move. Degeneracy: after many insertions in the same spot, gaps shrink toward machine precision. Solution: rebalance when min gap = wip_limit and the task is moving from a different column: reject with WIPLimitExceededError. If the task is already in the column (reordering within the column): allow (not adding a new task). Exception: allow managers to override WIP limits (role-based bypass). Visualization: display the column’s task count vs limit (3/5) in the UI. Highlight the column in red when at capacity. Alert the team when a column is perpetually at WIP limit (systemic bottleneck).”
}
},
{
“@type”: “Question”,
“name”: “How do you design a workflow automation rules engine for task management?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “A rules engine evaluates trigger-condition-action triples. Structure: Rule {id, board_id, name, trigger_type (TASK_MOVED, TASK_ASSIGNED, DUE_DATE_APPROACHING), trigger_config (JSON), condition_tree (JSON), actions (JSON array)}. Condition tree: {op: “AND”, children: [{field: “priority”, operator: “eq”, value: “HIGH”}, {field: “labels”, operator: “contains”, value: “bug”}]}. Action types: ASSIGN_USER, ADD_LABEL, MOVE_TO_COLUMN, SEND_NOTIFICATION, CREATE_SUBTASK. Evaluation: when a task event fires, load all rules for the board. For each rule matching the trigger type: evaluate the condition tree recursively against the task state. Execute all matching actions. Rules are evaluated synchronously for simple actions (assign, label) and asynchronously via a job queue for expensive actions (create subtask, send notification). Store rule execution history for debugging and auditing.”
}
},
{
“@type”: “Question”,
“name”: “How does real-time collaboration work when multiple users edit the same task?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Multiple users may edit a task description simultaneously (like Google Docs). Simple last-write-wins approach: the last save overwrites all concurrent edits. Easy to implement but causes lost edits. Operational Transformation (OT) or CRDTs: handle concurrent edits without conflict. Practical approach for task management (not a full doc editor): field-level locking — only one user can edit a field at a time. When a user starts editing: acquire a soft lock (stored in Redis, TTL 30 seconds, refreshed on keystrokes). Other users see the field as “being edited by [user]” and their edit is disabled. On save: release the lock. On conflict (lock expired, two users raced): show a merge UI. For comment editing: comments are append-only — no conflicts. Activity feed is append-only. The task title and description are the conflict-prone fields; most implementations use optimistic locking with a version field and show a conflict dialog on version mismatch.”
}
},
{
“@type”: “Question”,
“name”: “How do you implement @mention notifications in comments?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “When a comment is saved, parse the body for @username patterns: find all tokens matching /@(w+)/. For each mentioned username, look up the user_id (cached lookup from username to user_id). Validate that the mentioned user is a member of the workspace. Add the mentioned user as a task subscriber if not already subscribed. Send an immediate notification: “User A mentioned you in [task title].” Include the comment text and a link to the task. Deduplication: if the user is already subscribed and the task is already in their notification digest, skip adding a duplicate entry — just send the @mention notification. Rate limiting: if 10 users are @mentioned in one comment, send 10 notifications — acceptable. If a bot posts 100 comments each @mentioning 100 users: rate-limit the notification sender (max N mentions per comment, max M notifications per user per minute).”
}
}
]
}
Asked at: Atlassian Interview Guide
Asked at: LinkedIn Interview Guide
Asked at: Snap Interview Guide
Asked at: Twitter/X Interview Guide
See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering