Building Retry Logic That Actually Works
A 3-minute Claude API outage turned into a 2-hour cascading failure, because of how my retry logic responded to it.
I built a system that retried failed requests with a simple exponential backoff strategy. On a Tuesday, the Claude API had a brief outage: three minutes. By the time it recovered, my Lambda was retrying requests every 30 seconds, generating 10x the normal traffic. The brief outage became a cascading failure that lasted two hours.
Retry logic is one of those things that feels simple and is actually dangerous if you get it wrong.
The retry strategies
Strategy 1: immediate retry (bad)
This is bad because if the API is overwhelmed, you're hammering it immediately. Everyone retrying at once creates a thundering herd.
Strategy 2: linear backoff (better)
This spreads retries out over time: 2, 4, 6 seconds. Better than immediate retry, but still predictable. If 1,000 requests fail at the same time, they'll all retry at 2 seconds, then all retry again at 4 seconds.
Strategy 3: exponential backoff with jitter (good)
With exponential backoff and jitter:
- Attempt 1 fails at 0s: wait 2^0 = 1s + jitter (0-0.25s) = 1-1.25s
- Attempt 2 fails at about 1.1s: wait 2^1 = 2s + jitter (0-0.5s) = 2-2.5s
- Attempt 3 fails at about 3.2s: wait 2^2 = 4s + jitter (0-1s) = 4-5s
Total time: about 8-9 seconds across 3 attempts. Retries are spread out and not synchronized.
Distinguishing retryable vs. terminal errors
Not all errors should trigger a retry. If the API returns 400 (bad request), retrying won't help — you need to fix the input.
The key insight: 400 Bad Request will fail every time. Don't waste retries on it.
Idempotency: the critical requirement
Retry logic only works if your operation is idempotent: calling it multiple times produces the same result as calling it once.
Example: if your function reads a document, classifies it, and writes the result to a database, what happens if it retries?
Without idempotency:
- Call 1: classify document, write to database, succeed. But the response is lost to a network timeout.
- Retry: classify again, write again. Now you have two classification results in the database.
With idempotency:
- Introduce an idempotency key, for example a hash of the document.
- Before writing, check if this key already exists.
- If it does, return the cached result instead of classifying again.
Now, even if the request retries 5 times, you get the same classification without calling Claude 5 times.
Lambda retry configuration
AWS Lambda has built-in retry behavior for asynchronous invocations, for example from SQS.
This configuration:
- Retries failed messages up to 2 times.
- If a batch of 10 messages has 1 failure, splits the batch and retries the failing message.
- After 1 hour total, gives up and sends to the DLQ.
- Only requires you to return which specific messages failed, not retry yourself.
Cost implications
Uncontrolled retries can explode your costs.
Example: a bug causes 1,000 requests a day to fail with a retryable error. Without proper retry logic:
- Original 1,000 requests: 1,000 × $0.0135 = $13.50
- 3 automatic retries: 3,000 × $0.0135 = $40.50
- Total: $54/day, or $1,620/month for a single bug.
With proper exponential backoff plus a circuit breaker:
- Original 1,000 requests: $13.50
- 2 retries, not 3: about 2,000 × $0.0135 = $27
- Circuit breaker stops after detecting a high failure rate: no further retries.
- Total: about $40/day, or $1,200/month.
The circuit breaker pattern helps: if the error rate exceeds a threshold, stop retrying and fail fast.
When the circuit breaker trips, you fail fast instead of retrying, saving cost and reducing cascading failures.
Monitoring retries
Track retry behavior:
If "SuccessAfterRetry" is high, more than 20% of requests needing retries, your system is fragile. If it's very low, close to zero, either your system is very reliable or your retries aren't working.
The checklist
Before shipping retry logic:
- I've identified which errors are retryable vs. terminal.
- My operation is idempotent.
- I'm using exponential backoff and jitter, not linear or immediate retry.
- Maximum retry time is reasonable, not days.
- I have a circuit breaker to fail fast when the service is overwhelmed.
- I'm monitoring retry rate and success-after-retry rate.
- My DLQ has alerts configured.
Get this right and your system survives transient failures gracefully. Get it wrong, and you turn a 3-minute outage into a cascading disaster.
Want help hardening your error handling? Book a call and we'll walk through your retry logic together.
Get the free AI Readiness Checklist
15 questions to diagnose your team’s AI readiness, where you’ll see ROI fastest, and what to tackle first.
No spam. Unsubscribe anytime.
Ready to build AI that actually works?
Let’s talk about how SRE discipline transforms AI from a risky experiment into a reliable business system.
Start the Conversation