Implement Task Dependencies and Failure
Company: Perplexity
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: hard
Interview Round: Technical Screen
Quick Answer: This question evaluates understanding of graph-based data structures and algorithms for modeling bidirectional task dependencies, status tracking, and efficient cascading failure propagation in an in-memory task manager.
Constraints
- 0 <= len(operations) <= 200000
- The total number of unique tasks plus unique dependency edges is at most 200000
- Each `task_id` is a string of length 1 to 50
- The same task may be added multiple times, and the same dependency may be added multiple times
- The dependency graph may contain shared descendants or cycles
Examples
Input: ([('add_task', 'A'), ('add_task', 'B'), ('add_task', 'C'), ('add_dependency', 'B', 'A'), ('add_dependency', 'C', 'B'), ('fail_task', 'A'), ('get_status', 'A'), ('get_status', 'B'), ('get_status', 'C')],)
Expected Output: ['failed', 'failed', 'failed']
Explanation: B depends on A and C depends on B, so failing A cascades to B and then to C.
Input: ([('add_task', 'A'), ('add_task', 'B'), ('add_task', 'C'), ('add_task', 'D'), ('add_task', 'E'), ('add_dependency', 'B', 'A'), ('add_dependency', 'C', 'A'), ('add_dependency', 'D', 'B'), ('add_dependency', 'D', 'C'), ('add_dependency', 'D', 'C'), ('fail_task', 'A'), ('get_status', 'A'), ('get_status', 'B'), ('get_status', 'C'), ('get_status', 'D'), ('get_status', 'E')],)
Expected Output: ['failed', 'failed', 'failed', 'failed', 'active']
Explanation: D is reachable from A through both B and C, but it should still only be failed once. E is unrelated and stays active.
Input: ([('add_task', 'A'), ('add_task', 'B'), ('add_task', 'C'), ('add_dependency', 'C', 'B'), ('fail_task', 'A'), ('add_dependency', 'B', 'A'), ('get_status', 'A'), ('get_status', 'B'), ('get_status', 'C')],)
Expected Output: ['failed', 'failed', 'failed']
Explanation: A fails first. Later, B is declared to depend on failed A, so B must fail immediately, and C fails because it depends on B.
Input: ([('add_task', 'A'), ('add_task', 'B'), ('add_dependency', 'A', 'B'), ('add_dependency', 'B', 'A'), ('fail_task', 'A'), ('get_status', 'A'), ('get_status', 'B')],)
Expected Output: ['failed', 'failed']
Explanation: The cycle should not cause an infinite loop. Once A fails, B fails too, and already failed tasks are not processed again.
Input: ([('get_status', 'missing')],)
Expected Output: ['not_found']
Explanation: The task was never created or referenced, so its status is not_found.
Input: ([],)
Expected Output: []
Explanation: With no operations, there are no status queries to return.
Hints
- Use two hash maps of sets: one for each task's dependencies and one for each task's dependents.
- Failure only needs to travel from a task to its dependents. Use an iterative DFS/BFS and make sure each task is processed at most once when it becomes failed.