Skip to main content
Handbook/Harden/Chapter 92 · Background Jobs

Do Slow Work in the Background

Share

Share this page

Pass it to someone who needs it.

Key takeaway: Do slow work off the request

Some work is too slow to make a user wait on: sending an email, processing an uploaded file, calling a slow model. Do it inside the request and the page hangs on a spinner, or the request times out before the work finishes. This chapter gets that slow work off the request, so your app stays snappy no matter how heavy the job.

10.7.1Not all work fits in one request

A web request has one job: take the input and return a response fast, ideally in well under a second. Slow work does not belong inside it. While the request waits on the file or the model, the user sits watching a frozen page, and if it waits too long the request times out and fails.

The fix is a background job: work your app does after it has already responded to the user. The request hands the slow part off and returns at once, then the job runs on its own time.

10.7.2Respond now, work later

So split the request in two. Accept the input, hand the slow part to the background, and respond at once. The user hears "we are on it" in the same instant, while the heavy work runs after the response is already sent.

That is why a well-built app feels fast even when the work behind it is not. The user gets an immediate, honest status instead of a spinner that might time out, and nothing on their screen is blocked on the slow job.

The request responds at once while the slow work runs off the request path and reports back when done.

10.7.3A queue holds the work for a worker

Between your app and the slow work sits a queue: a line of jobs waiting to be run. Your app adds a job and moves on. A separate worker pulls jobs off the queue one at a time and runs each to completion, in its own process.

This is the same queue-and-worker setup that carries load when you scale out, aimed here at slow work instead of traffic. It buys retries almost for free: if a job throws, the queue hands it back to be run again, so a blip does not lose the work.

// In the request: save the input, enqueue a // job, and respond. The user waits on none of // the slow work below. async function handleUpload(req, res) { const file = await save(req.file) await queue.add('process-upload', { fileId: file.id, }) res.json({ status: 'processing' }) }
// The worker: pulls each job and runs the slow // work off the request path. If it throws, the // queue re-delivers it for another try. queue.process('process-upload', async (job) => { const { fileId } = job.data await processUpload(fileId) await setStatus(fileId, 'done') })

Watch out: the queue can deliver a job more than once, so make each job safe to run twice. Sending the same email or charging a card twice does real damage; this is the same safe-to-retry rule your risky writes already need.

10.7.4Tell the user when it is done

The work now finishes out of sight, so close the loop and tell the user when the job is done, or they are left wondering if it worked. Two honest ways:

  • The worker writes a status the user's page checks, processing then done, and the page updates itself.
  • The worker pushes a notification when it finishes: an email, or an in-app alert the user sees next time.

Either way the rule holds: never start background work without a way to report the result. A silent job that never reports back looks, to the user, exactly like one that failed.

This prompt moves your slow operations to the background:

Ready prompt
Act as a senior engineer moving my slow operations off the request path. Do not change what the app does, only when the work runs. Go through this feature and: 1. Find the slow work that blocks the response: sending email, processing files, or calling a model. Anything over a second counts. 2. Move each one to a background job. The request should enqueue the job and respond right away with a processing status. 3. Sketch the worker that pulls jobs and runs them, with retries on failure and each job safe to run more than once. 4. When a job finishes, report back: update a status the page can poll, or send the user a notification. Give me the slow spots first as a short list, then the change for each one. The feature to move to the background:

Do this now: paste the prompt with your slowest feature, the one that makes users wait, and let your agent move it to a background job that reports back when it is done.

Mahmoud Zalt

Mahmoud Zalt

Software engineer, 16+ yrs · built Sistava.com in 3 months, idea to production, using these methods

Resources
Contribute
Donate

Support my work

A small tip keeps the free work coming.

© 2026 Mahmoud Zalt. Free to read, not to republish.
Copyright & license