Implement Dependency-Aware To-Do List
Company: Perplexity
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates the ability to design in-memory data structures and algorithms for representing tasks with directed dependencies, manage state transitions and cascading updates, and reason about correctness, performance, and edge cases.
Part 1: Basic CRUD To-Do List
Constraints
- 0 <= len(operations) <= 100000
- 1 <= task_id <= 10^9
- Title/description is a string of length 0 to 200
- Valid statuses are exactly 'READY', 'BLOCKED', 'SUCCEEDED', and 'FAILED'
- For deterministic output, listed tasks must be sorted by task_id
Examples
Input: [('create', 1, 'Write code', 'READY'), ('create', 2, 'Review PR', 'BLOCKED'), ('get', 1), ('update', 2, 'Review pull request'), ('list', None), ('delete', 1), ('list', 'READY')]
Expected Output: [True, True, (1, 'Write code', 'READY'), True, [(1, 'Write code', 'READY'), (2, 'Review pull request', 'BLOCKED')], True, []]
Explanation: Covers normal create, read, update, delete, and list behavior.
Input: [('create', 1, 'A', 'READY'), ('create', 1, 'B', 'FAILED'), ('create', 2, 'C', 'DONE'), ('get', 3), ('update', 3, 'X'), ('delete', 2), ('list', None)]
Expected Output: [True, False, False, None, False, False, [(1, 'A', 'READY')]]
Explanation: Shows duplicate id handling, invalid status handling, and missing-id operations.
Input: []
Expected Output: []
Explanation: Edge case: no operations.
Input: [('create', 5, 'Only', 'SUCCEEDED'), ('list', 'SUCCEEDED'), ('list', 'FAILED'), ('list', 'UNKNOWN')]
Expected Output: [True, [(5, 'Only', 'SUCCEEDED')], [], []]
Explanation: Checks status filtering, including an invalid filter value.
Hints
- A hash map keyed by task_id makes create/get/update/delete constant time.
- For 'list', build the filtered tuples first, then sort them once by id.
Part 2: Dependency-Aware Tasks With Cascading Updates
Constraints
- 0 <= len(operations) <= 20000
- 1 <= task_id <= 10^9
- Title/description is a string of length 0 to 200
- Valid task statuses are 'READY', 'BLOCKED', 'SUCCEEDED', and 'FAILED'
- Direct 'set_status' operations are allowed only for 'SUCCEEDED' and 'FAILED'; 'READY' and 'BLOCKED' are maintained automatically
- The dependency graph must remain acyclic
Examples
Input: [('create', 1, 'A'), ('create', 2, 'B'), ('add_dependency', 1, 2), ('get', 2), ('set_status', 1, 'SUCCEEDED'), ('get', 2)]
Expected Output: [True, True, True, (2, 'B', 'BLOCKED', [1], []), True, (2, 'B', 'READY', [1], [])]
Explanation: Task 2 is blocked until task 1 succeeds, then it becomes ready automatically.
Input: [('create', 1, 'A'), ('create', 2, 'B'), ('create', 3, 'C'), ('add_dependency', 1, 2), ('add_dependency', 2, 3), ('set_status', 1, 'FAILED'), ('list',)]
Expected Output: [True, True, True, True, True, True, [(1, 'A', 'FAILED', [], [2]), (2, 'B', 'FAILED', [1], [3]), (3, 'C', 'FAILED', [2], [])]]
Explanation: A failure propagates recursively through the dependent chain.
Input: [('create', 1, 'A'), ('create', 2, 'B'), ('create', 3, 'C'), ('add_dependency', 1, 2), ('add_dependency', 2, 3), ('add_dependency', 3, 1), ('add_dependency', 1, 1), ('add_dependency', 1, 2), ('remove_dependency', 1, 2), ('get', 2)]
Expected Output: [True, True, True, True, True, False, False, False, True, (2, 'B', 'READY', [], [3])]
Explanation: Shows cycle rejection, self-dependency rejection, duplicate rejection, and unblocking after dependency removal.
Input: [('create', 1, 'Solo'), ('set_status', 9, 'FAILED'), ('set_status', 1, 'READY'), ('set_status', 1, 'SUCCEEDED'), ('set_status', 1, 'FAILED'), ('get', 1)]
Expected Output: [True, False, False, True, False, (1, 'Solo', 'SUCCEEDED', [], [])]
Explanation: Missing ids fail, READY cannot be set manually, and terminal tasks cannot be updated again.
Input: []
Expected Output: []
Explanation: Edge case: no operations.
Hints
- Store both directions of each dependency: parents and children. That makes downstream updates local.
- Maintain, for each task, how many dependencies have not yet succeeded. Then a child becomes READY exactly when this count drops to 0.