logo

Polling is the default pattern developers reach for when they need to know if an async job has finished. Create a task, then loop: check status, wait, check again. It works, but the costs accumulate in ways that stay hidden until you scale.

This guide quantifies the real cost of polling and shows when to use cheaper alternatives.

Step 1: Calculate the True Cost of Polling

Consider a polling loop that checks every 5 seconds:

const checkStatus = async (taskId) => {
while (true) {
const task = await aq.tasks.get(taskId);
if (['completed', 'failed', 'timeout'].includes(task.status)) {
return task;
}
await sleep(5000);
}
};

For a single task that takes 60 seconds to complete:

  • 12 API calls (one every 5 seconds)
  • 11 of those calls returned “still processing” and produced no value

Scale that to your workload:

Daily TasksAvg Completion TimePoll IntervalWasted API Calls/Day
10030 seconds5s500
1,00060 seconds5s11,000
10,0002 minutes5s230,000
10,00010 minutes5s1,190,000

Over a million wasted API calls per day, all to learn that jobs have finished. Every one of those calls costs compute, network bandwidth, and rate limit headroom.

Step 2: Understand the Latency Penalty

Polling carries a built-in delay. If your task completes 1 second after your last poll, you will not know for another 4 seconds (with a 5-second interval). On average, you detect completion halfway through the poll interval.

Poll IntervalAverage Detection LatencyWorst Case Latency
1 second500ms1 second
5 seconds2.5 seconds5 seconds
10 seconds5 seconds10 seconds
30 seconds15 seconds30 seconds

You can shrink the interval, but that multiplies API call volume. The trade-off is direct: lower latency means more wasted calls.

With a webhook or signal, detection latency drops to zero. The system notifies you the moment the task completes.

Step 3: Recognize Rate Limit Pressure

Every polling call counts against your rate limit. If your plan allows 600 requests per minute and you have 50 tasks polling every 5 seconds, those status checks consume 600 requests per minute, leaving zero headroom for creating new tasks or reading results.

Rate limit budget: 600 req/min
Polling overhead:
50 active tasks x 12 polls/min = 600 req/min (ALL of your budget)
Productive API calls:
Creating tasks: 0 remaining
Reading results: 0 remaining

This creates a counterintuitive failure mode: the busier your system gets, the more polling devours your rate limit, which slows down the productive work.

Webhooks and signals use 1 request per task completion. 50 task completions = 50 requests, not 600.

Step 4: Switch to Push-Based Alternatives

Alternative 1: onComplete webhook

The simplest replacement. AsyncQueue calls your endpoint when the task finishes:

// No polling needed
await aq.tasks.create({
targetUrl: 'https://your-app.com/api/process',
payload: { orderId: 'order_123' },
onCompleteUrl: 'https://your-app.com/api/task-done',
});
// Your webhook handler receives the result
app.post('/api/task-done', async (req, res) => {
const { task } = req.body;
await handleCompletion(task.id, task.result);
res.json({ received: true });
});

Cost: 0 polling calls. 1 webhook delivery.

Alternative 2: wait-for-signal

For tasks that depend on external events:

const { task, signalToken } = await aq.tasks.create({
targetUrl: 'https://your-app.com/api/start',
waitForSignal: true,
maxWaitTime: 3600,
});
// External system sends signal when ready
// No polling on your side at all

Cost: 0 polling calls. 1 signal request.

Alternative 3: client-side polling with smart intervals

If you must poll (e.g., showing progress in a UI), use adaptive intervals:

async function smartPoll(taskId) {
let interval = 1000; // Start at 1 second
const maxInterval = 15000; // Cap at 15 seconds
while (true) {
const task = await aq.tasks.get(taskId);
if (['completed', 'failed', 'timeout'].includes(task.status)) {
return task;
}
await sleep(interval);
interval = Math.min(interval * 1.5, maxInterval); // Slow down over time
}
}

This cuts total calls by 40-60% compared to fixed-interval polling.

Step 5: Use Polling Only When It Makes Sense

Polling is not always wrong. Use it when:

  • You are building a CLI tool that runs once and exits. A webhook endpoint makes no sense for a throwaway script.
  • Tasks complete in under 5 seconds. Two or three polls cause no harm.
  • You lack a publicly accessible endpoint to receive webhooks (local development, internal tools).
  • You are showing real-time progress in a UI and the user watches actively.

For everything else, prefer webhooks or signals:

ScenarioBest ApproachWhy
Background job, result needed lateronComplete webhookZero polling overhead
Waiting for external eventWait-for-signalZero polling, instant resolution
CLI script, quick taskPolling (short)No infrastructure needed
UI progress indicatorClient-side polling (adaptive)User expects real-time feedback
High-volume batch processingonComplete webhookPolling at scale destroys rate limits