A task management system is a software tool that helps individuals and teams plan, organize, assign, and track tasks in an efficient and structured manner. It plays a key role in improving productivity, accountability, and collaboration especially in fast-paced, team-driven environments.
Write unit tests
Setup CI/CD pipeline
Implement authentication
Design system architecture
Popular tools like Trello, Asana, and ClickUp are examples of task management platforms designed to streamline workflows and improve team efficiency.
In this chapter, we will explore the low-level design of a task management system in detail.
Lets start by clarifying the requirements:
Before starting the design, it's important to ask thoughtful questions to uncover hidden assumptions, clarify ambiguities, and define the system's scope more precisely.
Here is an example of how a discussion between the candidate and the interviewer might unfold:
Candidate: Should the system support task hierarchies, such as subtasks under a parent task?
Interviewer: Yes, tasks can have one or more subtasks.
Candidate: What metadata should be associated with a task?
Interviewer: Each task should have a title, description, due date, priority, tags, and a status like “To Do,†“In Progress,†or “Done.â€
Candidate: Should the system support grouping tasks under a task list or project?
Interviewer: Yes, tasks can be organized under a project or task list for better structure and management.
Candidate: Should users be able to perform a full-text search on task details?
Interviewer: Full-text search on task titles should be supported. In addition, filtering by status, assignee, priority and sorting by due date should also be supported.
Candidate: Should the system maintain an activity log for each task to track updates and progress over time?
Interviewer: Yes, we should log important events such as task creation,, status changes, and assignment modifications.
Candidate: Are there different user roles, and should permissions vary based on roles?
Interviewer: For now, we can keep it simple. Assume all users have the same level of access.
After gathering the details, we can summarize the key system requirements.
Core entities are the fundamental building blocks of our system. We identify them by analyzing the functional requirements and highlighting the key nouns and responsibilities that naturally map to object-oriented abstractions such as classes, enums, or interfaces.
Let’s walk through the functional requirements and extract the relevant entities:
This clearly indicates the need for a User entity to represent each participant in the system. The Task entity will be the central unit of work.
This metadata maps directly to fields within the Task entity.
TaskStatus, TaskPriority).Tag entity if we want more control (e.g., filtering or tag management).This suggests the need for a Project (or TaskList) entity to act as a container for related tasks. Each task will reference the project it belongs to.
This indicates the need for an ActivityLog entity, which records events like task creation, status changes, and assignment updates. Each log entry will reference the task it belongs to and include metadata like timestamp and action type.
User: Represents an individual user of the system. Responsible for creating tasks, updating them, assigning tasks, and receiving assignments.Task: The core unit of work. Contains metadata like title, description, due date, priority, status, tags, and a reference to its parent task (if any). Each task has a single assignee and belongs to one project.TaskStatus: Enum representing the state of a task. Possible values include TO_DO, IN_PROGRESS, DONE, etc.TaskPriority: Enum representing the urgency or importance of a task. Typical values include LOW, MEDIUM, HIGH.TaskList: Represents a collection of related tasks grouped under a shared context, such as a project or task list.Tag: Represents a label or keyword assigned to tasks for categorization and filtering. Can be modeled as a separate entity or a simple list of strings within a task.ActivityLog: Captures significant events related to a task, such as creation, updates, reassignment, and status changes. Includes timestamp, event type, and user involved.These core entities define the essential abstractions of a Task Management System and will guide the structure of our low-level design and class diagrams.
The system is broken down into several classes, each with a specific responsibility. They can be categorized as Enums, Data Classes, and Core Classes.
TaskStatus: Represents the distinct stages in a task's lifecycle (TODO, IN_PROGRESS, DONE, BLOCKED). This provides type-safe state management.TaskPriority: Defines the urgency levels for a task (LOW, MEDIUM, HIGH, CRITICAL), which is useful for sorting and filtering.These classes are primarily simple data containers with minimal logic.
UserRepresents an actor in the system.
Identified by a unique ID, name, and email. A User can create or be assigned to tasks.
TagA simple wrapper for a string that categorizes tasks (e.g., "feature", "bug"). Tags are reusable across multiple tasks.
CommentStores a piece of text content, the User who authored it, and a timestamp.
It provides a mechanism for discussion on a task.
ActivityLogRecords a single change made to a task with a description and a timestamp, creating an immutable audit trail.
These classes encapsulate the main business logic and behavior of the system.
TaskThe central entity of the system. It aggregates all relevant information like title, description, due date, priority, assignee, comments, and subtasks. It also manages its own state transitions and notifies observers of changes.
TaskListA container for a logical grouping of Task objects (e.g., a project's backlog or a sprint's tasks).
TaskManagementSystemThe main controller and entry point for the application. It acts as a central repository for all users, tasks, and lists, providing a simplified interface for all system operations.
The classes interact through several well-defined relationships, which helps in maintaining low coupling and high cohesion.
TaskManagementSystem has a Map of User, Task, and TaskList. The system owns and manages the lifecycle of these core objects.TaskList has a List of Tasks. The tasks belong to the list.Task has a List of Comments and ActivityLogs. These are integral parts of a task and do not exist independently.Task has a List of Tasks (subtasks), forming a recursive composition.Task is associated with a User as its assignee and createdBy. The User is an independent entity. This is a one-to-many relationship (one User can be assigned many Tasks).Comment is associated with a User (its author).Task is associated with a TaskState object, to which it delegates state-specific behavior.Task has a Set of Tags. A Tag like "bug" can exist on its own and be associated with multiple tasks.Task has a list of TaskObservers. The observers are independent entities that register with the task.This relationship exists between an interface and the concrete classes that implement it.
TodoState, InProgressState, and DoneState classes implement the TaskState interface.SortByPriority and SortByDueDate classes implement the TaskSortStrategy interface.ActivityLogger implements the TaskObserver interface.Several design patterns are employed to ensure the system is flexible, scalable, and maintainable.
The TaskState interface and its concrete implementations (TodoState, InProgressState, DoneState) manage the lifecycle of a Task. This pattern isolates state-specific logic, making it easy to add new states (e.g., BlockedState) without modifying the Task class.
The TaskSortStrategy interface allows different sorting algorithms (SortByPriority, SortByDueDate) to be encapsulated and used interchangeably. The client (TaskManagementSystem) can select a sorting strategy at runtime.
The TaskObserver pattern is used to notify dependent objects (ActivityLogger) of any change in a Task's state. This decouples the Task (the subject) from the objects that need to react to its changes (the observers).
The Task class can contain a collection of other Task objects (subtasks). This allows a client to treat an individual task and a group of tasks (a task with its subtasks) uniformly, as seen in the recursive display() method.
The Task class uses a nested static TaskBuilder to construct complex Task objects. This pattern handles numerous optional parameters cleanly and improves the readability of object creation.
The TaskManagementSystem also serves as a Facade, providing a simple, unified interface (createTask, createUser, etc.) to the more complex underlying subsystem of creating and linking tasks, users, builders, and observers.
The TaskManagementSystem class is implemented as a Singleton to ensure there is only one instance of it acting as the central point of control and data repository for the entire application.
TaskStatus and TaskPriority1class TaskStatus(Enum):
2 TODO = "TODO"
3 IN_PROGRESS = "IN_PROGRESS"
4 DONE = "DONE"
5 BLOCKED = "BLOCKED"
6
7class TaskPriority(Enum):
8 LOW = "LOW"
9 MEDIUM = "MEDIUM"
10 HIGH = "HIGH"
11 CRITICAL = "CRITICAL"These enums define task metadata:
TaskStatus tracks task lifecycle stages.TaskPriority indicates the urgency of the task, used for sorting and filtering.Represents a user in the system with a unique ID, name, and email. Used as creator or assignee for tasks.
1class User:
2 def __init__(self, name: str, email: str):
3 self._id = str(uuid.uuid4())
4 self._name = name
5 self._email = email
6
7 @property
8 def id(self) -> str:
9 return self._id
10
11 @property
12 def email(self) -> str:
13 return self._email
14
15 @property
16 def name(self) -> str:
17 return self._nameRepresents a tag (e.g., "bug", "feature") used to categorize tasks. Tags are optional metadata.
1class Tag:
2 def __init__(self, name: str):
3 self._name = name
4
5 @property
6 def name(self) -> str:
7 return self._nameModels a comment left on a task by a user, with timestamp for audit trail.
1class Comment:
2 def __init__(self, content: str, author: User):
3 self._id = str(uuid.uuid4())
4 self._content = content
5 self._author = author
6 self._timestamp = datetime.now()
7
8 @property
9 def author(self) -> User:
10 return self._authorTracks updates to a task like status changes, comments, assignment, etc. Used for task history and accountability.
1class ActivityLog:
2 def __init__(self, description: str):
3 self._description = description
4 self._timestamp = datetime.now()
5
6 def __str__(self) -> str:
7 return f"[{self._timestamp}] {self._description}"The Task class is the heart of the system. It's a complex entity that aggregates data and behavior to manage its state, relationships, and history.
1class Task:
2 def __init__(self, builder: 'TaskBuilder'):
3 self._id = builder._id
4 self._title = builder._title
5 self._description = builder._description
6 self._due_date = builder._due_date
7 self._priority = builder._priority
8 self._created_by = builder._created_by
9 self._assignee = builder._assignee
10 self._tags = builder._tags
11 self._current_state = TodoState() # Initial state
12 self._comments: List[Comment] = []
13 self._subtasks: List['Task'] = []
14 self._activity_logs: List[ActivityLog] = []
15 self._observers: List[TaskObserver] = []
16 self._lock = threading.Lock()
17 self.add_log(f"Task created with title: {self._title}")
18
19 def set_assignee(self, user: User):
20 with self._lock:
21 self._assignee = user
22 self.add_log(f"Assigned to {user.name}")
23 self.notify_observers("assignee")
24
25 def update_priority(self, priority: TaskPriority):
26 with self._lock:
27 self._priority = priority
28 self.notify_observers("priority")
29
30 def add_comment(self, comment: Comment):
31 with self._lock:
32 self._comments.append(comment)
33 self.add_log(f"Comment added by {comment.author.name}")
34 self.notify_observers("comment")
35
36 def add_subtask(self, subtask: 'Task'):
37 with self._lock:
38 self._subtasks.append(subtask)
39 self.add_log(f"Subtask added: {subtask.get_title()}")
40 self.notify_observers("subtask_added")
41
42 # State Pattern Methods
43 def set_state(self, state: TaskState):
44 self._current_state = state
45 self.add_log(f"Status changed to: {state.get_status().value}")
46 self.notify_observers("status")
47
48 def start_progress(self):
49 self._current_state.start_progress(self)
50
51 def complete_task(self):
52 self._current_state.complete_task(self)
53
54 def reopen_task(self):
55 self._current_state.reopen_task(self)
56
57 # Observer Pattern Methods
58 def add_observer(self, observer: TaskObserver):
59 self._observers.append(observer)
60
61 def remove_observer(self, observer: TaskObserver):
62 if observer in self._observers:
63 self._observers.remove(observer)
64
65 def notify_observers(self, change_type: str):
66 for observer in self._observers:
67 observer.update(self, change_type)
68
69 def add_log(self, log_description: str):
70 self._activity_logs.append(ActivityLog(log_description))
71
72 def is_composite(self) -> bool:
73 return len(self._subtasks) > 0
74
75 def display(self, indent: str = ""):
76 print(f"{indent}- {self._title} [{self.get_status().value}, {self._priority.value}, Due: {self._due_date}]")
77 if self.is_composite():
78 for subtask in self._subtasks:
79 subtask.display(indent + " ")
80
81 # Getters and setters
82 def get_id(self) -> str:
83 return self._id
84
85 def get_title(self) -> str:
86 return self._title
87
88 def get_description(self) -> str:
89 return self._description
90
91 def get_priority(self) -> TaskPriority:
92 return self._priority
93
94 def get_due_date(self) -> date:
95 return self._due_date
96
97 def get_assignee(self) -> Optional[User]:
98 return self._assignee
99
100 def set_title(self, title: str):
101 self._title = title
102
103 def set_description(self, description: str):
104 self._description = description
105
106 def get_status(self) -> TaskStatus:
107 return self._current_state.get_status()
108
109 # Builder Pattern
110 class TaskBuilder:
111 def __init__(self, title: str):
112 self._id = str(uuid.uuid4())
113 self._title = title
114 self._description = ""
115 self._due_date = None
116 self._priority = None
117 self._created_by = None
118 self._assignee = None
119 self._tags = set()
120
121 def description(self, description: str) -> 'Task.TaskBuilder':
122 self._description = description
123 return self
124
125 def due_date(self, due_date: date) -> 'Task.TaskBuilder':
126 self._due_date = due_date
127 return self
128
129 def priority(self, priority: TaskPriority) -> 'Task.TaskBuilder':
130 self._priority = priority
131 return self
132
133 def assignee(self, assignee: User) -> 'Task.TaskBuilder':
134 self._assignee = assignee
135 return self
136
137 def created_by(self, created_by: User) -> 'Task.TaskBuilder':
138 self._created_by = created_by
139 return self
140
141 def tags(self, tags: Set[Tag]) -> 'Task.TaskBuilder':
142 self._tags = tags
143 return self
144
145 def build(self) -> 'Task':
146 return Task(self)A task has many optional and required attributes, making its constructor complex. The Builder Pattern provides a clean, fluent API for creating Task objects.
1class TaskList:
2 def __init__(self, name: str):
3 self._id = str(uuid.uuid4())
4 self._name = name
5 self._tasks: List[Task] = []
6 self._lock = threading.Lock()
7
8 def add_task(self, task: Task):
9 with self._lock:
10 self._tasks.append(task)
11
12 def get_tasks(self) -> List[Task]:
13 with self._lock:
14 return self._tasks.copy() # Return a copy to prevent external modification
15
16 @property
17 def id(self) -> str:
18 return self._id
19
20 @property
21 def name(self) -> str:
22 return self._name
23
24 def display(self):
25 print(f"--- Task List: {self._name} ---")
26 for task in self._tasks:
27 task.display("")
28 print("-----------------------------------")Encapsulates a logical group of tasks (e.g., “Bugsâ€, “Featuresâ€). Allows displaying grouped tasks in a hierarchical view.
Implements the Strategy pattern to allow pluggable sorting mechanisms. New strategies (e.g., by creation date or assignee) can be added easily.
1class TaskSortStrategy(ABC):
2 @abstractmethod
3 def sort(self, tasks: List[Task]):
4 pass
5
6class SortByPriority(TaskSortStrategy):
7 def sort(self, tasks: List[Task]):
8 # Higher priority comes first (CRITICAL > HIGH > MEDIUM > LOW)
9 priority_order = {TaskPriority.CRITICAL: 4, TaskPriority.HIGH: 3,
10 TaskPriority.MEDIUM: 2, TaskPriority.LOW: 1}
11 tasks.sort(key=lambda task: priority_order.get(task.get_priority(), 0), reverse=True)
12
13class SortByDueDate(TaskSortStrategy):
14 def sort(self, tasks: List[Task]):
15 tasks.sort(key=lambda task: task.get_due_date() if task.get_due_date() else date.max)1class TaskObserver(ABC):
2 @abstractmethod
3 def update(self, task: 'Task', change_type: str):
4 pass
5
6class ActivityLogger(TaskObserver):
7 def update(self, task: 'Task', change_type: str):
8 print(f"LOGGER: Task '{task.get_title()}' was updated. Change: {change_type}")External components like loggers or notification systems can subscribe to task changes without being tightly coupled.
1class TaskState(ABC):
2 @abstractmethod
3 def start_progress(self, task: 'Task'):
4 pass
5
6 @abstractmethod
7 def complete_task(self, task: 'Task'):
8 pass
9
10 @abstractmethod
11 def reopen_task(self, task: 'Task'):
12 pass
13
14 @abstractmethod
15 def get_status(self) -> TaskStatus:
16 pass
17
18class TodoState(TaskState):
19 def start_progress(self, task: 'Task'):
20 task.set_state(InProgressState())
21
22 def complete_task(self, task: 'Task'):
23 print("Cannot complete a task that is not in progress.")
24
25 def reopen_task(self, task: 'Task'):
26 print("Task is already in TO-DO state.")
27
28 def get_status(self) -> TaskStatus:
29 return TaskStatus.TODO
30
31class InProgressState(TaskState):
32 def start_progress(self, task: 'Task'):
33 print("Task is already in progress.")
34
35 def complete_task(self, task: 'Task'):
36 task.set_state(DoneState())
37
38 def reopen_task(self, task: 'Task'):
39 task.set_state(TodoState())
40
41 def get_status(self) -> TaskStatus:
42 return TaskStatus.IN_PROGRESS
43
44class DoneState(TaskState):
45 def start_progress(self, task: 'Task'):
46 print("Cannot start a completed task. Reopen it first.")
47
48 def complete_task(self, task: 'Task'):
49 print("Task is already done.")
50
51 def reopen_task(self, task: 'Task'):
52 task.set_state(TodoState())
53
54 def get_status(self) -> TaskStatus:
55 return TaskStatus.DONEEach TaskState implementation (TodoState, InProgressState, DoneState) knows which transitions are valid from its current state. This design cleans up the Task class and makes it easy to add new states (e.g., BlockedState) without modifying existing code.
This class serves as the central point of control. It uses the Singleton Pattern to ensure a single instance manages all system data and the Facade Pattern to provide a simple, unified interface to the complex subsystem.
1class TaskManagementSystem:
2 _instance = None
3 _lock = threading.Lock()
4
5 def __new__(cls):
6 if cls._instance is None:
7 with cls._lock:
8 if cls._instance is None:
9 cls._instance = super().__new__(cls)
10 cls._instance._initialized = False
11 return cls._instance
12
13 def __init__(self):
14 if not self._initialized:
15 self._users: Dict[str, User] = {}
16 self._tasks: Dict[str, Task] = {}
17 self._task_lists: Dict[str, TaskList] = {}
18 self._initialized = True
19
20 @classmethod
21 def get_instance(cls):
22 return cls()
23
24 def create_user(self, name: str, email: str) -> User:
25 user = User(name, email)
26 self._users[user.id] = user
27 return user
28
29 def create_task_list(self, list_name: str) -> TaskList:
30 task_list = TaskList(list_name)
31 self._task_lists[task_list.id] = task_list
32 return task_list
33
34 def create_task(self, title: str, description: str, due_date: date,
35 priority: TaskPriority, created_by_user_id: str) -> Task:
36 created_by = self._users.get(created_by_user_id)
37 if created_by is None:
38 raise ValueError("User not found.")
39
40 task = Task.TaskBuilder(title) \
41 .description(description) \
42 .due_date(due_date) \
43 .priority(priority) \
44 .created_by(created_by) \
45 .build()
46
47 task.add_observer(ActivityLogger())
48
49 self._tasks[task.get_id()] = task
50 return task
51
52 def list_tasks_by_user(self, user_id: str) -> List[Task]:
53 user = self._users.get(user_id)
54 return [task for task in self._tasks.values()
55 if task.get_assignee() == user]
56
57 def list_tasks_by_status(self, status: TaskStatus) -> List[Task]:
58 return [task for task in self._tasks.values()
59 if task.get_status() == status]
60
61 def delete_task(self, task_id: str):
62 if task_id in self._tasks:
63 del self._tasks[task_id]
64
65 def search_tasks(self, keyword: str, sorting_strategy: TaskSortStrategy) -> List[Task]:
66 matching_tasks = []
67 for task in self._tasks.values():
68 if (keyword in task.get_title() or
69 keyword in task.get_description()):
70 matching_tasks.append(task)
71
72 sorting_strategy.sort(matching_tasks)
73 return matching_tasksThe TaskManagementSystemDemo class demonstrates how a client would interact with the system's facade to perform common operations.
1class TaskManagementSystemDemo:
2 @staticmethod
3 def main():
4 task_management_system = TaskManagementSystem.get_instance()
5
6 # Create users
7 user1 = task_management_system.create_user("John Doe", "[email protected]")
8 user2 = task_management_system.create_user("Jane Smith", "[email protected]")
9
10 # Create task lists
11 task_list1 = task_management_system.create_task_list("Enhancements")
12 task_list2 = task_management_system.create_task_list("Bug Fix")
13
14 # Create tasks
15 task1 = task_management_system.create_task(
16 "Enhancement Task", "Launch New Feature",
17 date.today().replace(day=date.today().day + 2),
18 TaskPriority.LOW, user1.id
19 )
20 subtask1 = task_management_system.create_task(
21 "Enhancement sub task", "Design UI/UX",
22 date.today().replace(day=date.today().day + 1),
23 TaskPriority.MEDIUM, user1.id
24 )
25 task2 = task_management_system.create_task(
26 "Bug Fix Task", "Fix API Bug",
27 date.today().replace(day=date.today().day + 3),
28 TaskPriority.HIGH, user2.id
29 )
30
31 task1.add_subtask(subtask1)
32
33 task_list1.add_task(task1)
34 task_list2.add_task(task2)
35
36 task_list1.display()
37
38 # Update task status
39 subtask1.start_progress()
40
41 # Assign task
42 subtask1.set_assignee(user2)
43
44 task_list1.display()
45
46 # Search tasks
47 search_results = task_management_system.search_tasks("Task", SortByDueDate())
48 print("\nTasks with keyword Task:")
49 for task in search_results:
50 print(task.get_title())
51
52 # Filter tasks by status
53 filtered_tasks = task_management_system.list_tasks_by_status(TaskStatus.TODO)
54 print("\nTODO Tasks:")
55 for task in filtered_tasks:
56 print(task.get_title())
57
58 # Mark a task as done
59 subtask1.complete_task()
60
61 # Get tasks assigned to a user
62 user_task_list = task_management_system.list_tasks_by_user(user2.id)
63 print(f"\nTask for {user2.name}:")
64 for task in user_task_list:
65 print(task.get_title())
66
67 task_list1.display()
68
69 # Delete a task
70 task_management_system.delete_task(task2.get_id())
71
72if __name__ == "__main__":
73 TaskManagementSystemDemo.main()This driver code simulates a user's interaction with the system, showcasing:
Which is the most appropriate entity to log changes such as task creation or status updates in a task management system design?
taskList1->addTask(task1);
he seems to be doing that with this right? what i mean is the created tasks are appended to this tasklist list/vector.task management system can have probably a double checked locking or static instance as i think static instance are thread safe now.
In TaskManagementSystem you are just creating taskList, where as while creating task you are never assigning it to any task list. So please fix it as it is supporting the functional requirement