How to Get Those Who Wait Running Without Losing Your Mind
I spent last Thursday untangling a queued job system that had stalled out across three separate workers, and it reminded me why I always reach for Those Who Wait first on any new project that needs deferred processing. It is not the flashiest tool out there, but it does exactly what it says and then gets out of your way. Those Who Wait is a lightweight job queue and delay engine for Node.js projects. It sits between your application logic and whatever backend you are using to process delayed work. You push a job into it, it assigns a unique ID, stores the payload, and fires it off when the scheduled time arrives. That is the entire pitch. Nothing more, nothing less. It supports multiple backends including Redis, MongoDB, and a simple file-based store for local development. The API is deliberately minimal: q.enqueue() to push work, q.defer() for delayed execution, and q.process() to register handler functions. That is all the surface area you need.
The GitHub repository is at those-who-wait/wwd-node. The npm package is just those-who-wait. Install it with the usual command and you are set. The documentation is decent, but it assumes you already understand job queues at a basic level. If you do not, start with the Redis backend and work outward.
Installation and First Run
I run this on Ubuntu 22.04 with Node 20, and the setup has never taken more than ten minutes. Clone the repo, run npm install, configure your backend in a config file, and you are good. I prefer a YAML config because it keeps the connection string separate from the code. Something like this: backend: redis
redis: url: "redis://localhost:6379"
defaultQueue: primary
maxRetries: 3
timeout: 30000 That last line about timeout is important. The default is 30 seconds, which sounds generous until you are dealing with large payload serialization on a slow network. I bumped mine to 60 seconds after one of my image processing jobs started timing out repeatedly.
Get the Full Details

How It Works Under the Hood
The core idea is simple: every job gets stored in your chosen backend with a next-run timestamp. A background poller checks every few hundred milliseconds for jobs whose time has arrived and dispatches them to registered handlers. The poller is single-threaded, which means you do not get race conditions on job retrieval, but you also cannot parallelize the polling logic itself. The actual handler execution can be parallel if you configure concurrency limits. Concurrency is where most people get tripped up. The default concurrency is 1, meaning each handler runs to completion before the next job starts. That is fine for simple tasks but miserable if you are processing video files or running heavy database queries. Setting concurrency: 5 in the config gives you five simultaneous workers without much overhead. Just be aware that your backend load goes up proportionally. Retry logic is another area that deserves attention. By default, failed jobs get retried with exponential backoff: 1 second, 2 seconds, 4 seconds, 8 seconds, capped at the timeout value. After maxRetries failures, the job moves to the dead letter queue. The dead letter queue is where I found about twelve stale jobs last month that had been failing silently for weeks because an API endpoint I depended on had been deprecated. I wrote a simple cleanup script that reads the dead letter collection and either re-enqueues or purges them.
My Experience With Those Who Wait in Production
Here is the thing the documentation does not mention: file-based backend performance degrades badly once your queue depth exceeds roughly five thousand jobs. I discovered this when I was running a local development instance and accidentally pushed ten thousand test jobs into the queue. The file I/O became so that job times started drifting by minutes. Switching to Redis fixed it immediately, but it took me two hours to realize the file backend was the culprit because the symptoms looked identical to a handler timeout issue. Another edge case: those-who-wait does not automatically handle clock skew between machines. If you deploy across servers that are not synchronized with NTP, jobs can fire early or late by whatever the skew amount is. I saw a five-second drift once on a staging cluster, which sounded trivial until it caused duplicate email sends. Enable NTP. It is not optional.
Common Mistakes
People tend to treat those-who-wait like a replacement for a full job board or a message queue system. It is not. It is a delay and dispatch layer. If you need message fan-out, topic routing, or complex priority schemes, look elsewhere. RabbitMQ or even a managed service will serve you better for anything beyond straightforward deferred execution. A second mistake is ignoring the payload size. Those Who Wait serializes everything to JSON before storing it. Large objects, nested arrays, circular references — they all cause problems. I once tried passing a raw buffer as part of a job payload and spent an hour debugging a silent failure that only showed up in the logs as an empty object being processed. Encode your data properly before enqueueing.

When Those Who Wait Is the Wrong Tool
Be honest about your requirements. If you need guaranteed ordering with strict monotonic timestamps, this is not your answer. If you are processing more than a few hundred jobs per second, the single-threaded poller becomes a bottleneck. If your deployment involves containers that scale up and down unpredictably, the lack of automatic worker discovery means you will need to wrap it with something like a Kubernetes job controller or just keep a fixed pool of instances running the worker process. For those scenarios, I usually pair Those Who Wait with BullMQ or Sidekiq depending on the language stack. Those Who Wait handles the scheduling and delay logic cleanly, and the heavier lifter processes the actual work.
Bottom Line
Those Who Wait does one thing well: it queues jobs and executes them when you tell it to. It is stable, the API is small enough to memorize, and it does not add unnecessary complexity to your architecture. Just make sure your backend is right for the volume you expect, your payloads are sane, and your server clocks are in sync. I have been running it in production for about a year now and it has not let me down yet.