Retry with Backoff Pattern in C#: Definition, Architecture, Examples, Pros, and Cons
The Retry with Backoff Pattern is a resilience pattern in C# that automatically retries failed operations while gradually increasing the delay between retry attempts.
The Retry with Backoff Pattern helps applications recover from temporary or transient failures such as network interruptions, service throttling, or database timeouts. Instead of retrying immediately after a failure, the pattern waits for progressively longer delays before each retry attempt. This reduces pressure on overloaded systems and increases the probability of successful recovery. In C#, Retry with Backoff is commonly implemented using loops, timers, asynchronous delays, or resilience libraries such as Polly. The pattern is widely used in cloud-native systems, microservices, distributed systems, and external API integrations.
Why We Use Retry with Backoff Pattern in C#?
We use the Retry with Backoff Pattern to improve resilience against temporary failures.
Main reasons include:
• Handles transient faults
• Reduces temporary failure impact
• Improves reliability
• Prevents immediate retry storms
• Reduces load on failing services
• Improves distributed system stability
• Supports cloud-native architectures
• Increases operation success rates
• Works well with microservices
• Improves user experience during outages
What Is a Transient Failure?
A transient failure is a temporary issue that may succeed if retried later.
Examples:
• Temporary network timeout
• Database deadlock
• Cloud service throttling
• Short API outage
Backoff Strategies
1. Fixed Backoff
Uses a constant retry delay.
Example:
Retry 1 -> Wait 2 sec
Retry 2 -> Wait 2 sec
Retry 3 -> Wait 2 sec
2. Linear Backoff
Delay increases linearly.
Example:
Retry 1 -> Wait 2 sec
Retry 2 -> Wait 4 sec
Retry 3 -> Wait 6 sec
3. Exponential Backoff
Delay doubles after each retry.
Example:
Retry 1 -> Wait 2 sec
Retry 2 -> Wait 4 sec
Retry 3 -> Wait 8 sec
Retry 4 -> Wait 16 sec
Most commonly used strategy.
4. Exponential Backoff with Jitter
Adds randomization to avoid synchronized retries.
Example:
Retry 1 -> Wait 2.3 sec
Retry 2 -> Wait 4.7 sec
Retry 3 -> Wait 8.1 sec
Recommended for distributed systems.
Retry Workflow
Request
|
Success?
|
YES -> Complete
|
NO
|
Wait Backoff Delay
|
Retry
|
Max Retry Reached?
|
YES -> Fail
NO -> Retry Again
When Should We Use Retry with Backoff Pattern?
The Retry with Backoff Pattern should be used when failures are temporary and recoverable.
Typical programming problems where Retry with Backoff is beneficial:
• External API communication
• Cloud service integrations
• Database deadlocks
• Temporary network failures
• Distributed systems
• Microservices communication
• HTTP 429 throttling responses
• Message broker communication
• File upload/download systems
• Background job processing
Avoid Retry with Backoff for:
• Permanent failures
• Invalid input errors
• Authentication failures
• Business rule violations
• Non-idempotent operations without safeguards
• Immediate consistency requirements
Retry with Backoff Pattern Examples in C#
Example 1: Basic Retry with Fixed Backoff in C#
public async Task ExecuteWithRetryAsync()
{
int maxRetries = 3;
int delayMilliseconds = 2000;
for (int retry = 1; retry <= maxRetries; retry++)
{
try
{
Console.WriteLine("Executing operation");
throw new Exception("Temporary failure");
}
catch
{
Console.WriteLine($"Retry attempt: {retry}");
if (retry == maxRetries)
{
throw;
}
await Task.Delay(delayMilliseconds);
}
}
}
Example 2: Exponential Backoff in C#
public async Task ExecuteWithExponentialBackoffAsync()
{
int maxRetries = 5;
for (int retry = 1; retry <= maxRetries; retry++)
{
try
{
Console.WriteLine("Calling external service");
throw new Exception("Service unavailable");
}
catch
{
int delay = (int)Math.Pow(2, retry) * 1000;
Console.WriteLine(
$"Retry {retry} after {delay} ms");
await Task.Delay(delay);
}
}
}
Example 3: Exponential Backoff with Jitter
public async Task ExecuteWithJitterAsync()
{
Random random = new Random();
int maxRetries = 5;
for (int retry = 1; retry <= maxRetries; retry++)
{
try
{
Console.WriteLine("Processing request");
throw new Exception("Transient failure");
}
catch
{
int exponentialDelay =
(int)Math.Pow(2, retry) * 1000;
int jitter =
random.Next(0, 1000);
int totalDelay =
exponentialDelay + jitter;
Console.WriteLine(
$"Retry {retry} after {totalDelay} ms");
await Task.Delay(totalDelay);
}
}
}
Example 4: Retry with Polly
Install Package
dotnet add package Polly
// Polly Retry with Backoff
using Polly;
var retryPolicy = Policy
.Handle<Exception>()
.WaitAndRetryAsync(
retryCount: 5,
sleepDurationProvider: retryAttempt =>
TimeSpan.FromSeconds(
Math.Pow(2, retryAttempt)),
onRetry: (exception, timeSpan, retryCount, context) =>
{
Console.WriteLine(
$"Retry {retryCount} after {timeSpan.TotalSeconds} sec");
});
await retryPolicy.ExecuteAsync(async () =>
{
Console.WriteLine("Calling API");
throw new Exception("Temporary error");
});
Example 5: Retry with HttpClientFactory
// Service Registration
builder.Services.AddHttpClient("ApiClient")
.AddTransientHttpErrorPolicy(policy =>
policy.WaitAndRetryAsync(
3,
retryAttempt =>
TimeSpan.FromSeconds(
Math.Pow(2, retryAttempt))
));
// Usage
public class ApiService
{
private readonly HttpClient _client;
public ApiService(IHttpClientFactory factory)
{
_client = factory.CreateClient("ApiClient");
}
public async Task<string> GetAsync()
{
return await _client.GetStringAsync(
"https://www.howcsharp.com/api");
}
}
Example 6: Retry Only for Specific Errors
var retryPolicy = Policy
.Handle<HttpRequestException>()
.Or<TimeoutException>()
.WaitAndRetryAsync(
3,
retry => TimeSpan.FromSeconds(retry)
);
Advantages of Retry with Backoff Pattern in C#
| Advantage | Description |
|---|---|
| Handles Transient Failures | Improves reliability during temporary outages. |
| Reduces System Pressure | Backoff delays prevent aggressive retry storms. |
| Improves Resilience | Applications recover more gracefully. |
| Simple Implementation | Easy to implement using loops or Polly. |
| Works Well in Distributed Systems | Suitable for cloud-native applications. |
| Supports Scalability | Prevents overload amplification. |
| Highly Configurable | Retry counts and delays can be customized. |
Disadvantages (Weak Points) of Retry with Backoff Pattern in C#
| Disadvantage | Description |
|---|---|
| Increased Latency | Retries add additional waiting time. |
| Not Suitable for Permanent Failures | Retries cannot fix invalid operations. |
| Possible Retry Storms | Poorly configured retries may overload systems. |
| Complex Tuning | Backoff intervals require careful adjustment. |
| Duplicate Requests | Retries may repeat operations unintentionally. |
| Resource Consumption | Large numbers of retries consume threads and memory. |
Retry with Backoff vs Similar Patterns
| Feature | Retry with Backoff | Circuit Breaker | Timeout Pattern | Bulkhead Pattern | Fallback Pattern |
|---|---|---|---|---|---|
| Main Goal | Recover from transient failures | Prevent repeated failures | Limit wait duration | Isolate resources | Provide backup response |
| Failure Handling | Retries failed operations | Blocks requests temporarily | Cancels slow operations | Separates workloads | Returns alternative result |
| Resource Protection | Moderate | Excellent | Moderate | Excellent | Moderate |
| Complexity | Low to Medium | Medium | Low | Medium | Medium |
| Best For | Temporary outages | Repeated failures | Long-running operations | High-concurrency systems | Graceful degradation |
| State Management | No | Yes | No | Yes | Optional |
| Works with Microservices | Excellent | Excellent | Good | Excellent | Excellent |
Common Real-World Use Cases
Cloud API Communication
Workflow:
API rate limit exceeded
-> Wait with exponential backoff
-> Retry request
Database Deadlock Recovery
Workflow:
Transaction deadlock detected
-> Delay retry
-> Re-execute transaction
Message Broker Connectivity
Workflow:
Broker temporarily unavailable
-> Retry connection with backoff
Popular C# Libraries for Retry with Backoff
| Library | Description |
|---|---|
| Polly | Most popular .NET resilience library. |
| Microsoft.Extensions.Http.Polly | ASP.NET Core integration for Polly policies. |
| Steeltoe | Cloud-native resilience framework. |
| Refit | REST API library supporting Polly integration. |
| YARP | Reverse proxy supporting retry middleware. |
Best Practices for Retry with Backoff Pattern
• Use exponential backoff with jitter
• Retry only transient failures
• Limit maximum retry attempts
• Combine with Circuit Breaker Pattern
• Use timeout policies
• Ensure operations are idempotent
• Monitor retry frequency
• Avoid infinite retries
• Log retry attempts
Summary
The Retry with Backoff Pattern is a resilience pattern in C# designed to recover from temporary failures by retrying operations with progressively increasing delays. It is widely used in distributed systems, cloud-native applications, microservices, and external API communication. Exponential backoff with jitter is the most recommended strategy because it reduces synchronized retry storms and improves stability. Although retries increase latency and require careful tuning, the pattern significantly improves reliability and fault tolerance in modern distributed applications.
Data Management Patterns in C#
23. CQRS (Command Query Responsibility Segregation) in C#
24. Event Sourcing in C#
25. Saga Pattern in C#
26. Outbox Pattern in C#
Microservice Patterns in C#
27. Circuit Breaker Pattern in C#
28. Bulkhead Pattern in C#
29. Retry with Backoff Pattern in C#