Implement a multithreaded task executor with semaphores
Company: SoFi
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates understanding of concurrent programming, thread-safety, semaphore-style resource management, exception-safe permit release, and extensible API design, and it falls under the Coding & Algorithms category testing concurrency/multithreading, synchronization primitives, and resource management.
Constraints
- 0 <= len(tasks) <= 200000
- 1 <= max_threads <= 10^9
- For every task, 1 <= permits <= max_threads
- 0 <= duration <= 10^9
- `kind` is either `'M'` or `'D'`
- For divide tasks with `value != 0`, assume `value` divides `input_value` exactly
Examples
Input: (3, [('M', 2, 2, 5, 4), ('M', 5, 1, 4, 7), ('D', 3, 2, 2, 18), ('D', 0, 1, 1, 9), ('D', 3, 3, 2, 27)])
Expected Output: [(0, 5, None), (0, 4, None), (5, 7, 6), (7, 8, 'ERROR'), (8, 10, 9)]
Explanation: The first two multiply tasks start immediately at time 0 and consume all 3 permits. The first divide task must wait until time 5 to get 2 permits. The divide-by-zero task returns 'ERROR' but still releases its permit at time 8, allowing the final task to start.
Input: (5, [])
Expected Output: []
Explanation: No tasks are submitted, so the schedule is empty.
Hints
- Keep a min-heap of currently running tasks ordered by finish time. When you need more permits, jump time forward to the next finishing task and release all tasks that end then.
- The caller's current time only advances in two situations: when waiting to acquire permits, or when a blocking divide task is running. Fire-and-forget multiply tasks do not advance the caller's time.