ConcurrencyTypeScriptverifiedVerified
Thread Pool Pattern in TypeScript
Maintain a fixed set of reusable worker threads that pick up tasks from a queue, avoiding the overhead of spawning a new thread per task.
How to Implement the Thread Pool Pattern in TypeScript
1Step 1: Define the task type
type Task<T> = () => Promise<T>;2Step 2: Implement the pool with queuing and dispatch
class ThreadPool {
private queue: Array<{
task: Task<unknown>;
resolve: (v: unknown) => void;
reject: (e: unknown) => void;
}> = [];
private activeCount = 0;
constructor(private size: number) {}
submit<T>(task: Task<T>): Promise<T> {
return new Promise<T>((resolve, reject) => {
this.queue.push({
task: task as Task<unknown>,
resolve: resolve as (v: unknown) => void,
reject,
});
this.dispatch();
});
}
private dispatch(): void {
while (this.activeCount < this.size && this.queue.length > 0) {
const entry = this.queue.shift()!;
this.activeCount++;
entry.task()
.then(entry.resolve)
.catch(entry.reject)
.finally(() => {
this.activeCount--;
this.dispatch();
});
}
}
get pending(): number { return this.queue.length; }
get active(): number { return this.activeCount; }
}3Step 3: Submit tasks and observe pooled execution
const pool = new ThreadPool(4);
const results = await Promise.all(
Array.from({ length: 10 }, (_, i) =>
pool.submit(async () => {
await new Promise(r => setTimeout(r, Math.random() * 100));
return `task-${i}-done`;
})
)
);
console.log(results);// ── Worker Pool with Node.js Worker Threads and Graceful Shutdown ──
import { Worker, isMainThread, parentPort, workerData } from "worker_threads";
import { EventEmitter } from "events";
interface WorkerTask {
id: string;
fn: string; // serialized function body
args: unknown[];
}
interface WorkerPoolOptions {
size: number;
taskTimeoutMs?: number;
idleTimeoutMs?: number;
}
interface PoolStats {
active: number;
idle: number;
queued: number;
completed: number;
failed: number;
}
class WorkerPool extends EventEmitter {
private workers: Array<{ worker: Worker; busy: boolean }> = [];
private queue: Array<{
task: WorkerTask;
resolve: (v: unknown) => void;
reject: (e: Error) => void;
}> = [];
private stats = { completed: 0, failed: 0 };
private shuttingDown = false;
constructor(
private scriptPath: string,
private options: WorkerPoolOptions
) {
super();
for (let i = 0; i < options.size; i++) {
this.spawnWorker();
}
}
private spawnWorker(): void {
const worker = new Worker(this.scriptPath);
const entry = { worker, busy: false };
this.workers.push(entry);
worker.on("message", ({ id, result, error }) => {
entry.busy = false;
const pending = this.queue.find(q => q.task.id === id);
if (error) {
this.stats.failed++;
pending?.reject(new Error(error));
} else {
this.stats.completed++;
pending?.resolve(result);
}
this.queue = this.queue.filter(q => q.task.id !== id);
this.dispatch();
});
worker.on("error", err => {
entry.busy = false;
this.stats.failed++;
this.emit("worker-error", err);
if (!this.shuttingDown) this.spawnWorker();
this.workers = this.workers.filter(w => w !== entry);
});
}
submit<T>(task: WorkerTask): Promise<T> {
if (this.shuttingDown) {
return Promise.reject(new Error("Pool is shutting down"));
}
return new Promise<T>((resolve, reject) => {
const timeoutId = this.options.taskTimeoutMs
? setTimeout(() => reject(new Error(`Task ${task.id} timed out`)),
this.options.taskTimeoutMs)
: null;
this.queue.push({
task,
resolve: (v) => { if (timeoutId) clearTimeout(timeoutId); resolve(v as T); },
reject: (e) => { if (timeoutId) clearTimeout(timeoutId); reject(e); },
});
this.dispatch();
});
}
private dispatch(): void {
const idle = this.workers.find(w => !w.busy);
if (!idle || this.queue.length === 0) return;
const next = this.queue.find(q => {
const alreadyRunning = this.workers.some(w => w.busy);
return !alreadyRunning || true;
});
if (!next) return;
idle.busy = true;
idle.worker.postMessage(next.task);
}
poolStats(): PoolStats {
return {
active: this.workers.filter(w => w.busy).length,
idle: this.workers.filter(w => !w.busy).length,
queued: this.queue.length,
...this.stats,
};
}
async shutdown(graceful = true): Promise<void> {
this.shuttingDown = true;
if (graceful) {
while (this.queue.length > 0 || this.workers.some(w => w.busy)) {
await new Promise(r => setTimeout(r, 50));
}
}
await Promise.all(this.workers.map(w => w.worker.terminate()));
this.workers = [];
}
}
export { WorkerPool, type WorkerTask, type WorkerPoolOptions, type PoolStats };Thread Pool Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Thread Pool Pattern in the Real World
“A hotel concierge desk staffed by three concierges represents the thread pool. No matter how many guests check in, only three requests are handled simultaneously. Other guests wait in the lobby queue. When a concierge finishes, they immediately assist the next waiting guest — the staff are never created or dismissed per guest, they simply stay on duty.”