DEV Community

Harvey Landyn
Harvey Landyn

Posted on

Retrying Failed API Requests: A Small Feature With Big Consequences

Retries are one of those backend techniques that seem straightforward.

A request fails, so the application tries again. If the next request succeeds, the user never notices the temporary problem.

But not every failed request should be retried automatically.

I think this is an important distinction for anyone building reliable web applications.

The problem with assuming a failure means nothing happened

Imagine a frontend sends a request to create a payment or submit an order.

The server processes the request, but the response gets lost because of a network timeout.

From the client's perspective, the request failed.

From the server's perspective, the operation may have completed successfully.

If the client blindly sends the same request again, it could create a duplicate operation.

This is why a timeout doesn't necessarily mean the original action wasn't completed.

The same reasoning applies to financial applications, where repeating an operation can have real consequences.

Idempotency makes retries safer

One useful concept here is idempotency.

An operation is idempotent when repeating it has the same intended effect as performing it once.

For example, setting a user's notification preference to false can be designed as an idempotent operation.

Creating a new payment is different. Repeating the same request might create another payment unless the system has safeguards.

For operations that support it, an API can accept an idempotency key. The server associates repeated requests with the same logical operation and avoids executing it multiple times.

The exact implementation depends on the application, but the principle is straightforward: retries should not accidentally duplicate work.

Not every error deserves another attempt

I'd also separate temporary failures from permanent ones.

A transient network issue or a rate-limit response may justify retrying after an appropriate delay.

An invalid request usually won't become valid just because the client sends it again.

A common approach is exponential backoff: increase the delay between attempts rather than repeatedly hitting a struggling service.

Jitter can also help prevent many clients from retrying at exactly the same time.

The important part is to define retry behavior deliberately, including a maximum number of attempts and a clear stopping condition.

How I'd approach this in a real project

Before adding automatic retries, I'd ask:

Is the operation safe to repeat?

Can the server identify duplicate requests?

Which errors are temporary?

Should the client respect a server-provided retry delay?

What happens when every attempt fails?

Can the user understand the final state?

I'd also make sure that the interface doesn't report an operation as failed when its outcome is still unknown.

Sometimes the correct next step is to check the status of the original operation rather than create a new one.

Final thoughts

Retries can make an application more resilient, but poorly designed retries can introduce new problems.

Reliable systems don't simply repeat requests until something works. They account for uncertainty, distinguish different failure types, and protect operations from unintended duplication.

It's a small implementation detail that can have a major impact on reliability.

How do you usually handle retries in your projects: automatic backoff, idempotency keys, or a combination of both?

Top comments (0)