Katkat

Design and Implement a Worker Thread Pool with Futures and Shutdown

MicrosoftSoftware Engineer · Other

Last updated

Low-level design and concurrency question: design and implement a fixed-size thread pool with task submission, futures that return results or errors, and graceful or cancelling shutdown. It tests lock and condition variable use, lost wake-ups, policies for rejected work, pool sizing and deadlocks between dependent tasks.

Design and implement a thread pool: a fixed set of worker threads that run tasks submitted by other threads, so the application does not start a new thread for every task. Model the classes first, then implement the parts where concurrency bugs live: submitting a task, the worker loop, delivering results and errors back to the submitter, and shutting the pool down.

As in other low-level design rounds, the interviewer looks for clear class responsibilities and correct implementations of the key methods rather than every helper.

Constraints and Clarifications

  • Any thread may submit tasks at any time, including code that is itself running inside the pool.
  • The submitter must be able to wait for a task's result and must see any exception the task raised.
  • Shutdown must not leave worker threads running or callers waiting forever.
  • You may use the language's mutexes, condition variables and threads, but not its built-in executor or thread-pool classes.

Clarifying Questions

  • Is the number of workers fixed at creation, or should it grow and shrink with load?
  • Should the task queue be bounded, and what should happen to a submission when it is full?
  • On shutdown, should queued tasks that have not started still run, or be cancelled?
  • Do tasks need priorities, delays or repeating schedules?

Part 1 — Model the pool

Define the public API (creating the pool, submitting work, waiting for results, shutting down) and the classes behind it: what each one owns, the state it holds, and how the pieces interact over a task's lifetime.

What This Part Should Cover

  • A clear API, including a handle the caller uses to obtain the result or error
  • Responsibilities of the pool, the task queue, the workers and the result handle
  • Lifecycle states of the pool and of an individual task
  • Configuration: worker count, queue bound and what happens to rejected work

Part 2 — Implement the core

Implement submit, the worker loop, the result handle and shutdown, with both a graceful variant that finishes queued work and one that cancels it. Explain which lock protects which state, and why your workers neither spin while idle nor miss a wake-up.

What This Part Should Cover

  • Correct use of a lock and condition variable around the queue
  • Running the task outside the lock, with exceptions captured into the result handle
  • Shutdown with no lost wake-ups and no caller left waiting forever
  • Behavior when the queue is full or the pool has already shut down

Hints

Hint 1

What the submitter keeps: Think about what object the caller holds after submitting, and how it gets connected to whichever worker eventually runs the task.

Hint 2

Waking and re-checking: Ask what a worker should do when it wakes up and finds the queue still empty, and who wakes sleeping workers when the pool shuts down.

Follow-up questions

  • A task submits a second task to the same pool and waits for its result. When can that deadlock, and how do you prevent it?
  • How would you let the pool grow up to a maximum size under load and shrink back when idle?
  • How would you add delayed and repeating tasks?
  • How would you choose the number of workers for CPU-bound versus I/O-bound workloads?