The edges of the system: async work, lifetimes, tests, and deployment

FastAPI's route contract lives inside several larger systems: Python's execution model, the request and application lifecycles, the test harness, and the deployment environment. This chapter examines what changes when work waits, resources outlive a function, connections stay open, or one process becomes several workers. The practical judgment is to choose function shapes, resource scopes, tests, and infrastructure separately rather than expecting a route declaration to solve every runtime problem.

Three clocks govern a FastAPI application

The first clock is the request. A client connects, FastAPI resolves dependencies, the endpoint runs, a response is produced, and request-scoped resources are eventually cleaned up. The second clock is the application lifespan. Startup work happens before the application accepts requests, and shutdown work happens after it has finished handling them. The third clock is the operating-system process. It starts the server program, owns memory, can crash, and may be restarted or replicated by something outside the FastAPI application.

Confusing these clocks creates subtle bugs. A database session that belongs to one request should not be treated as a process-wide object. A machine-learning model that is expensive to load for each request may belong in application lifespan or another explicitly shared scope. A migration that must run once should not be placed in code that every worker executes independently. The declaration-centered model remains useful, but its meaning depends on which lifetime owns the state.

Async execution adds another distinction. An asynchronous function can pause at an await while it waits for an I/O operation such as a database query, a remote API, or network data. The server can use that time to make progress on other requests. This is concurrency: work overlaps while tasks wait. It is not the same as parallelism, where multiple processors or processes execute computation at the same time.

The difference is practical. A web API often spends much of its time waiting for clients, databases, or remote services. One event-loop process can keep many such operations in progress without dedicating a blocked thread to every wait. A task that performs image processing, large matrix calculations, or another CPU-heavy operation has a different profile: it occupies computation rather than waiting. Adding await around it does not make the computation interruptible in a useful way, and the application may need worker processes, a queue, or a separate service.

FastAPI supports both async def and regular def path operations and dependencies. An asynchronous callable is awaited. A synchronous callable is run in an external threadpool so it does not directly block the event-loop path. That lets an endpoint use a blocking library without forcing the surrounding ASGI application to call it synchronously on the event loop. It does not remove the cost of the blocking operation, and it does not make CPU-heavy work an efficient I/O task.

The choice should follow the library being called. If a database client exposes an awaitable operation, an endpoint using it should be asynchronous. If a library has no async support and performs blocking I/O, a regular function may be the honest choice. If the endpoint performs only small computation and no waiting, either form can be possible, but the surrounding workload still determines the useful behavior. FastAPI's flexibility is a dispatch mechanism, not a reason to label every function async by habit.

The same principle applies to dependencies. A dependency can be asynchronous even when its endpoint is regular, or regular when its endpoint is asynchronous.