Displaying a book's title and generating a downloadable catalog involve different expectations. A reader needs the title to finish loading a page. A partner requesting a large catalog export may be willing to return for the file once it is ready.
The API must tell each caller what its response means: has the requested work finished, or has the service only accepted it?
This chapter explains synchronous and asynchronous API interactions, how they differ from blocking and non-blocking code, and how to choose an approach that fits the task.
In a synchronous API interaction, the caller receives the outcome of the requested operation through the original request–response exchange. The service performs the work it needs to produce that outcome before completing the response.
In an asynchronous API interaction, the initial exchange can finish before the requested business operation completes. The client learns the eventual outcome through a separate interaction, such as a status request or a notification.
These definitions describe the externally visible contract. They do not specify whether the server uses threads, an event loop, or a queue internally.
The operation's scope matters. If an API promises to create an order, a successful response can mean that the order exists even though packing and delivery happen later. That does not make order creation asynchronous. If the API promises to generate a catalog file and returns before the file is ready, file generation has not yet completed.
Use a concrete completion condition: “The client has received the book details,” “The reservation is active,” or “The export file is ready to download.” Without one, “the request succeeded” can hide disagreement between the provider and the client.
Suppose the bookstore app requests details for book_1042. The catalog service looks up the book and returns its details. Once the app receives the successful response body, it has the information it needs to display the page.
The diagram shows the work within that exchange:
The app must wait for the result before displaying the book details. Other independent activity can continue if the client implementation allows it.
This model keeps many interactions straightforward. A caller can request data, inspect the outcome, and continue without maintaining a separate job identifier or status-checking workflow. The service can also report validation failures and completed business decisions through the same exchange.
The request remains outstanding while the service works. A timeout is a limit on how long a participant waits. If the client, an intermediary, or the server reaches its limit, the caller may stop waiting before receiving an outcome.
Synchronous does not mean instantaneous or guaranteed to succeed. It describes where the caller expects the outcome. If the connection fails, the caller may still be uncertain whether a state-changing operation completed.
Now suppose an approved partner requests an export of the bookstore's catalog. Building the file may involve reading many records, formatting them, and storing a downloadable result. Assume that this work can take longer than the integration should keep its initial request open.
The API can accept the export request, return an identifier, and finish the work separately. A worker is a process responsible for performing that background work. A queue holds work waiting for a worker to process it; it is one possible implementation, not a requirement of asynchronous APIs.
The following example uses a worker and status polling, which means checking the operation's state through repeated requests:
The client does not keep the original request open until generation finishes. The service must instead keep enough state to link the accepted request to its eventual outcome.
This example assumes the service durably records accepted work, meaning it survives an ordinary process restart. That is a promise the bookstore chooses to implement. An acceptance response alone does not prove that a queue exists or that the service has safely stored the work.
The client might learn the outcome through a webhook or another notification mechanism instead of polling. The defining property is that operation completion is separate from the initial exchange. Microsoft describes this separation in its asynchronous request-reply pattern.
The fictional partner submits a request to https://api.bookstore.example/catalog-exports. In this example, export operations require an authorized partner identity. EXAMPLE_TOKEN is a nonfunctional credential placeholder.
The HTTP/1.1 request over HTTPS asks for CSV, a text format for tabular data:
After checking the request and recording the work, the service returns:
HTTP defines 202 Accepted to mean that the service has accepted the request for processing but has not completed it. It does not guarantee eventual success. HTTP also does not send a second, final status code on that completed exchange when the background operation finishes.
The identifier, status values, and statusUrl field are choices for this API. The relative URL resolves against the same service origin. This is one way to communicate a status resource; HTTP does not require these particular JSON fields.
The partner later checks the export through a separate request:
If the export has completed, the service responds:
Each JSON body occupies one line without a trailing newline. Cache-Control: no-store tells caches not to store these responses.
There are two different outcomes to interpret. 200 OK means the status retrieval succeeded. The body field status: "succeeded" means file generation succeeded. A successful status retrieval could instead report that generation is still running or has failed.
The download URL identifies a separate result request. In this example, the partner needs permission for that export to read its status or download its file; knowing the identifier does not grant access. The file request is separate from generating the file.
This creates an asynchronous workflow from ordinary HTTP request and response exchanges. Each status lookup can itself be synchronous even though the overall export operation is asynchronous.
Programming libraries also use the word asynchronous. In that context, it often describes how code waits for input or output.
A blocking call holds up the calling thread until it returns. A thread is a sequence of execution within a program. A non-blocking interface lets the program arrange to handle a result later while the program can continue other work.
For example, JavaScript's fetch returns a promise, an object representing an eventual result. An await expression inside an async function suspends that function while waiting rather than blocking the JavaScript thread. The response body may require a further asynchronous read.
Using await fetch(...) to retrieve book details does not turn the remote operation into a background job. The book lookup can still deliver its completed result through the original response. The client library simply allows other work while that response is pending.
Conversely, a client could use a blocking HTTP library to submit a catalog export. That library call waits for the acceptance response and returns while file generation continues.
The remote contract answers when the business operation completes. The client library answers how local code waits. Neither choice determines the other.
An asynchronous operation has two outcomes: whether the service accepts the request and whether processing succeeds. Admission means deciding whether the service will accept the request for processing.
Reject requests with problems the service can detect before acceptance, such as malformed inputs or missing permission. Other failures may occur only during execution, after the client has received an identifier.
For the export example, the distinction is:
The service needs to represent the outcome beyond a generic “accepted” flag. A minimal illustrative lifecycle makes the distinction visible:
Here, succeeded and failed are terminal states, outcomes after which this example performs no more work for the export. The diagram omits cancellation and internal retry behavior to keep the completion model focused.
Asynchronous processing does not eliminate uncertain outcomes. If the service records acceptance but the client never receives the initial response, blindly resubmitting can create a second export. If duplicate work matters, define how clients recover. For example, let the caller supply an attempt ID and document how the service handles repeated submissions with that ID.
Likewise, stopping a client request or closing a browser does not automatically cancel accepted work. If the API supports cancellation, define how clients request it and what cancellation does. Clients also need to know how long they can retrieve status and results, rather than treating a missing status record as proof that an export never existed.
A synchronous operation fits when the caller needs the result to continue and the service can normally complete the work within the time limit for the request. Book lookups and small validation operations often have this shape.
An asynchronous operation fits when completion time is long or variable, callers can continue without the result, or the service needs to schedule work independently. A large export is a useful candidate because the partner can request it and retrieve it later.
There is no universal duration after which an API must become asynchronous. Consider the client experience, request timeouts across the system, workload variation, and the consequences of making the caller wait.
Separate acknowledgment latency, the time until the service confirms acceptance, from completion latency, the time until the requested result is ready. An asynchronous API may reduce the first while leaving the second unchanged or increasing it through queueing. A faster acknowledgment is not a faster export.
A queue can absorb a brief surge in incoming work, but it does not create processing capacity. If work consistently arrives faster than workers finish it, the backlog grows. The service still needs limits on accepted work and a way to handle overload.
Asynchronous design also moves complexity. The client tracks progress or handles notifications. The provider stores operation state, manages background execution, and makes failures discoverable. Synchronous design avoids some of that coordination, but it keeps the request outstanding until the operation produces its result.
A product can combine both approaches. Retrieving export status is a short synchronous operation; generating the export is asynchronous. Choose synchronous or asynchronous behavior for each operation based on what the caller needs to know and when. The whole API does not have to use one model.
Synchronous API interactions return an operation's outcome through the original exchange. Asynchronous interactions separate acceptance from later completion, so callers need another way to learn the final result. Neither model determines whether client code blocks while waiting.
Choose based on the operation's completion condition, duration, and consumer workflow. Asynchronous processing can release the initial request sooner, but it introduces state tracking, additional failure points, and responsibility for accepted work.