Implement Dependency-Aware To-Do List
PerplexitySoftware Engineer · Technical ScreenLast updated
Design an in-memory task management system that tracks directed dependencies between tasks, prevents cycles, and unblocks dependent tasks upon completion.
Problem Description
Design an in-memory dependency-aware to-do list system. Tasks can have directed dependencies: a task cannot be started until all of its prerequisite tasks have been completed.
Implement the DependencyTodoList class with the following methods:
-
add_task(task_id: str) -> bool: Adds a new task. ReturnsTrueif the task was added successfully. If a task withtask_idalready exists, returnsFalse. -
add_dependency(task_id: str, prerequisite_id: str) -> bool: Declares thatprerequisite_idmust be completed beforetask_idcan be worked on. ReturnsFalseif:- Either
task_idorprerequisite_iddoes not exist. task_id == prerequisite_id(self-dependency).- The dependency already exists.
- Either task has already been completed.
- Adding this dependency would introduce a cycle.
Otherwise, records the dependency and returns
True.
- Either
-
complete_task(task_id: str) -> list[str]: Markstask_idas completed. A task can only be completed if it exists, is not already completed, and all of its prerequisites are already completed. Returns a lexicographically sorted list of task IDs that transitioned to "ready" (i.e., all their prerequisites are now satisfied) as a direct result of completing this task. Iftask_idcannot be completed, returns an empty list[]. -
get_ready_tasks() -> list[str]: Returns a lexicographically sorted list of all task IDs that are currently uncompleted and have all their prerequisites satisfied.
Example
todo = DependencyTodoList()
todo.add_task("design") # returns True
todo.add_task("code") # returns True
todo.add_task("test") # returns True
todo.add_dependency("code", "design") # returns True: "design" must precede "code"
todo.add_dependency("test", "code") # returns True: "code" must precede "test"
todo.get_ready_tasks() # returns ["design"]
# "code" cannot be completed yet because "design" is not completed
todo.complete_task("code") # returns []
# Completing "design" unblocks "code"
todo.complete_task("design") # returns ["code"]
todo.get_ready_tasks() # returns ["code"]
# Completing "code" unblocks "test"
todo.complete_task("code") # returns ["test"]
todo.complete_task("test") # returns []
todo.get_ready_tasks() # returns []
Constraints
1 <= len(task_id), len(prerequisite_id) <= 50- Task IDs consist only of alphanumeric characters and underscores.
- At most
2,000total calls to all methods combined. - The total number of unique tasks will not exceed
1,000.
Hints
Hint 1
Model the tasks as nodes in a directed graph where an edge points from a prerequisite to its dependent task.
Hint 2
To detect cycles before adding an edge from prerequisite_id to task_id, run a reachability check (BFS or DFS) to see if a path already exists from task_id to prerequisite_id.
Hint 3
Maintain the count of unsatisfied prerequisites (in-degree) for each pending task to check readiness and cascade updates in O(1) per edge during completion.
Follow-up questions
- How would you optimize `add_dependency` cycle detection if dependency additions were significantly more frequent than task completions?
- How would you extend this design to support task cancellation or rollbacks (marking a completed task back to pending)?
- How would you make this class thread-safe if multiple worker threads are completing tasks concurrently?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.