Python FastAPI building a single-threaded queue of jobs after API call
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
If an API call should enqueue work and return immediately, FastAPI can do that without introducing Celery or Redis on day one. The cleanest in-process design is an asyncio.Queue with exactly one worker task consuming jobs in FIFO order. That gives you sequential execution, predictable ordering, and a fast HTTP response path, as long as you understand that the queue lives only inside one application process.
Why BackgroundTasks alone is not a queue
FastAPI's BackgroundTasks runs work after the response is sent, which is useful for lightweight follow-up actions. It does not, by itself, give you a single global worker, job ordering, or shared backpressure. If two requests arrive together, two background tasks can run independently.
When you need "one job at a time," you want a real queue plus one consumer. In an async FastAPI app, asyncio.Queue is a natural fit.
Build a single-worker queue with lifespan startup
The pattern below starts one worker when the app boots, accepts jobs through POST /jobs, and lets clients check progress through GET /jobs/job_id.
This is single-threaded in the sense that one async worker processes one queued job at a time. Other requests can still be served while the queue worker awaits network or disk work, but the jobs themselves are serialized by the single consumer.
Keep the job function async and non-blocking
The queue stays responsive only if the worker does not block the event loop for long stretches. If a job is CPU-heavy or calls blocking libraries, move that part into a thread pool or process pool. For simple I/O-heavy tasks, an async function is enough.
Using asyncio.to_thread() keeps the FastAPI event loop from freezing while still preserving the one-job-at-a-time queue order.
Understand the process boundary
This design works only inside one FastAPI process. If you run Uvicorn or Gunicorn with multiple workers, each worker process gets its own in-memory queue and its own status dictionary. That breaks the idea of one global ordered queue.
So if you need strict single-worker semantics, run one application worker. If you later need durability, retries, or multiple machines, move the queue to an external broker such as Redis and use a proper task system.
That is also why this approach is best for lightweight internal workloads, not mission-critical job processing where jobs must survive restarts.
Common Pitfalls
The most common mistake is using BackgroundTasks and assuming it guarantees serialized execution. It does not. It simply schedules work after the response.
Another issue is running multiple ASGI worker processes. That creates multiple independent queues, which defeats the "single queue" requirement.
Blocking code inside the worker is another common problem. A long synchronous operation can stall the whole event loop if you do not move it to asyncio.to_thread() or an external worker.
Finally, remember that an in-memory queue loses pending jobs on process restart. If you need durability, this design is too small for the requirement.
Summary
- Use
asyncio.Queueplus one worker task when you need sequential job processing after an API call. - Start the worker with FastAPI lifespan so it is created once per process.
- Return
202 Acceptedfrom the enqueue endpoint and track job status separately. - Keep blocking job logic off the event loop, for example with
asyncio.to_thread(). - Run only one app worker if you need one true in-process queue.
Related reading
- Python Flask, how to set content type
- Python geventbottle. Querying an API. How to use gevent to prevent timeout locks?
- Python Request Post with param data
- Python Requests - No connection adapters
- Python file-based queue that is process-safe
- Python find a duplicate in a container efficiently
- Python find closest key in a dictionary from the given input key
- Python find closest string from a list to another string

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.