Asynchronous Programming
Web Services
Non-Blocking Calls
Immediate Response
API Optimization

Execute web service method and return immediately

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

If a web service operation takes a long time, the usual solution is not to keep the HTTP request open forever. The better pattern is to accept the work, return immediately, and process the job asynchronously in the background while the client checks status or receives a later callback.

Why Immediate Return Matters

Long-running HTTP requests are fragile. Clients time out, load balancers cut connections, and server threads or workers stay occupied longer than necessary. Returning immediately improves responsiveness and makes the service easier to scale.

The common design is:

  • client submits work
  • server enqueues the job
  • server returns a job identifier
  • background worker performs the real task
  • client polls a status endpoint or receives a webhook

This is an API pattern, not just a language feature.

The HTTP Shape: 202 Accepted

If the server has accepted the request but not completed the work, 202 Accepted is usually the most accurate HTTP response.

http
1HTTP/1.1 202 Accepted
2Content-Type: application/json
3
4{
5  "jobId": "7f6b3d92",
6  "status": "queued"
7}

This tells the client that the request was valid and work has started asynchronously, but the final result is not available yet.

A Simple Implementation Pattern

Here is a small FastAPI example that enqueues work and returns immediately:

python
1from fastapi import FastAPI, BackgroundTasks
2
3app = FastAPI()
4job_store = {}
5
6def process_job(job_id: str, payload: dict) -> None:
7    job_store[job_id] = {"status": "running"}
8    # Simulate expensive work
9    result = payload["value"] * 2
10    job_store[job_id] = {"status": "done", "result": result}
11
12@app.post("/jobs/{job_id}", status_code=202)
13def create_job(job_id: str, payload: dict, background_tasks: BackgroundTasks):
14    job_store[job_id] = {"status": "queued"}
15    background_tasks.add_task(process_job, job_id, payload)
16    return {"jobId": job_id, "status": "queued"}
17
18@app.get("/jobs/{job_id}")
19def get_job(job_id: str):
20    return job_store[job_id]

This demonstrates the idea clearly, even though production systems usually push jobs to a real queue instead of an in-memory store.

Background Tasks Versus Real Queues

Small apps can sometimes use framework background tasks or thread pools. Larger systems usually need a queue such as RabbitMQ, SQS, Redis-backed workers, or a service bus.

The difference is reliability:

  • in-process background tasks are simple but tied to the web process
  • message queues survive restarts and allow independent worker scaling

If the work matters enough that you cannot lose it on server restart, use a real queue.

Polling, Webhooks, or Both

Once the request returns immediately, the client still needs the result. The common patterns are:

  • polling a status endpoint with the job ID
  • server-to-server webhook callback
  • event streaming for interactive systems

Polling is simpler and more common. Webhooks are better when the client can expose a callback URL and wants push-style completion.

Do Not Confuse async Code with Async API Design

A language's async and await keywords help avoid blocking threads, but they do not automatically give you the correct API contract for long-running work. You can still have an async handler that keeps the request open for thirty seconds.

The deeper design question is whether the client should wait for the final answer at all. If not, use asynchronous API semantics, not just asynchronous syntax.

Common Pitfalls

  • Returning 200 OK immediately without giving the client a way to track the real job.
  • Doing long work in-process when failures or restarts would lose the job.
  • Treating async language syntax as if it solved long-running API design by itself.
  • Forgetting idempotency when clients retry a job-creation request.
  • Returning immediately without a status endpoint, webhook, or other completion mechanism.

Summary

  • Long-running service operations are usually better modeled as accepted background jobs.
  • '202 Accepted is the common HTTP response for this pattern.'
  • Return a job ID so the client can track progress.
  • Use a real queue when the work must survive restarts and scale independently.
  • Separate "non-blocking code" from "asynchronous API contract" because they are not the same thing.

Course illustration
Course illustration

All Rights Reserved.