Algorithm
Exponential Backoff Algorithm
Core Formula
delay = base_delay * 2^attempt
Attempt 0: base * 1
Attempt 1: base * 2
Attempt 2: base * 4
Attempt 3: base * 8
Attempt 4: base * 16
Implementation
def exponential_backoff(attempt, base_delay=1, max_delay=60):
"""Calculate exponential backoff delay"""
delay = base_delay * (2 ** attempt)
return min(delay, max_delay)
# Timeline:
# Attempt 0: 1s
# Attempt 1: 2s
# Attempt 2: 4s
# Attempt 3: 8s
# Attempt 4: 16s
# Attempt 5: 32s (capped at 60s)
Complete Retry Implementation
class ExponentialBackoff:
def __init__(self, max_retries=5, base_delay=1, max_delay=60):
self.max_retries = max_retries
self.base_delay = base_delay
self.max_delay = max_delay
def retry(self, func, is_retryable=None):
for attempt in range(self.max_retries):
try:
return func()
except Exception as e:
if is_retryable and not is_retryable(e):
raise
if attempt < self.max_retries - 1:
delay = self.calculate_delay(attempt)
time.sleep(delay)
else:
raise
def calculate_delay(self, attempt):
delay = self.base_delay * (2 ** attempt)
return min(delay, self.max_delay)
Why Exponential?
Linear: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
Exponential: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512
Exponential grows faster:
- Gives service time to recover
- Reduces load exponentially
- Prevents overwhelming failing service
Jitter
Jitter in Exponential Backoff
Why Jitter?
Without Jitter:
- All clients retry at same time
- Creates thundering herd
- Overwhelming spike at each retry
With Jitter:
- Clients retry at different times
- Smoother load distribution
- Better system stability
Jitter Types
import random
# 1. Full Jitter
def full_jitter(base, attempt, max_delay=60):
delay = base * (2 ** attempt)
return min(random.uniform(0, delay), max_delay)
# 2. Equal Jitter
def equal_jitter(base, attempt, max_delay=60):
delay = base * (2 ** attempt)
half = delay / 2
return min(half + random.uniform(0, half), max_delay)
# 3. Decorrelated Jitter
def decorrelated_jitter(base, attempt, prev_delay=0, max_delay=60):
if prev_delay == 0:
return base
return min(max_delay, random.uniform(base, prev_delay * 3))
AWS Recommended
def aws_backoff_jitter(attempt, base=1, max_delay=20):
"""AWS recommended algorithm"""
delay = min(max_delay, base * (2 ** attempt))
return random.uniform(0, delay)
Comparison
| Type | Range | Distribution | Best For |
|---|---|---|---|
| Full | [0, max] | Uniform | General use |
| Equal | [half, max] | Uniform | Balanced |
| Decorrelated | [base, 3x prev] | Variable | Adaptive |
Implementation
class ExponentialBackoffWithJitter:
def __init__(self, max_retries=5, base_delay=1, max_delay=60):
self.max_retries = max_retries
self.base_delay = base_delay
self.max_delay = max_delay
def retry(self, func, is_retryable=None):
prev_delay = 0
for attempt in range(self.max_retries):
try:
return func()
except Exception as e:
if is_retryable and not is_retryable(e):
raise
if attempt < self.max_retries - 1:
delay = self.calculate_delay(attempt, prev_delay)
prev_delay = delay
time.sleep(delay)
else:
raise
def calculate_delay(self, attempt, prev_delay):
base = self.base_delay * (2 ** attempt)
jitter = random.uniform(0, base * 0.1) # 10% jitter
return min(base + jitter, self.max_delay)
Implementation
Exponential Backoff Implementation
Production Implementation
import time
import random
import logging
class RetryHandler:
def __init__(self, max_retries=5, base_delay=1, max_delay=60):
self.max_retries = max_retries
self.base_delay = base_delay
self.max_delay = max_delay
self.logger = logging.getLogger(__name__)
def execute_with_retry(self, func, *args, **kwargs):
"""Execute function with exponential backoff retry"""
last_exception = None
for attempt in range(self.max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
if attempt < self.max_retries - 1:
delay = self._calculate_delay(attempt)
self.logger.warning(
f"Attempt {attempt + 1} failed: {e}. "
f"Retrying in {delay:.2f}s..."
)
time.sleep(delay)
else:
self.logger.error(
f"All {self.max_retries} attempts failed: {e}"
)
raise last_exception
def _calculate_delay(self, attempt):
"""Calculate delay with jitter"""
base = self.base_delay * (2 ** attempt)
jitter = random.uniform(0, base * 0.1)
return min(base + jitter, self.max_delay)
# Usage
retry_handler = RetryHandler(max_retries=5, base_delay=1)
result = retry_handler.execute_with_retry(call_external_api, param1, param2)
With Circuit Breaker
class ResilientCaller:
def __init__(self):
self.retry_handler = RetryHandler()
self.circuit_breaker = CircuitBreaker()
def call(self, func, *args, **kwargs):
if self.circuit_breaker.is_open:
raise CircuitOpenError('Circuit is open')
try:
result = self.retry_handler.execute_with_retry(
func, *args, **kwargs
)
self.circuit_breaker.record_success()
return result
except Exception as e:
self.circuit_breaker.record_failure()
raise
Monitoring
def monitor_retries(retry_handler):
metrics = {
'retry_count': get_retry_count(),
'retry_rate': get_retry_rate(),
'success_after_retry': get_retry_success_rate()
}
if metrics['retry_rate'] > 0.1:
alert('High retry rate detected')
Best Practices
- Always use jitter with exponential backoff
- Set max_delay cap (e.g., 60 seconds)
- Log retry attempts for debugging
- Monitor retry rates
- Combine with circuit breaker
Practice Problems
Design a scalable Exponential Backoff system. Cover high-level architecture, data model, and API design.
Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliabilityHow would you scale Exponential Backoff to handle 10x the current load? Identify bottlenecks and solutions.
Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decompositionAnalyze potential failure modes for Exponential Backoff and design mitigation strategies.
Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradationQuiz
1. What is the exponential backoff formula?
2. Why add jitter to exponential backoff?
3. What is the max delay cap?
4. What is full jitter?
5. Why combine with circuit breaker?
Flashcards
Question
Exponential backoff formula?
Click to reveal answer
Answer
delay = base_delay * 2^attempt (1s, 2s, 4s, 8s, 16s...)
Question
Why jitter with backoff?
Click to reveal answer
Answer
Prevents thundering herd by randomizing retry times so clients don't retry simultaneously
Question
What is max delay cap?
Click to reveal answer
Answer
Maximum allowed delay between retries (e.g., 60s) to prevent excessively long waits
Question
Full vs equal jitter?
Click to reveal answer
Answer
Full: random(0, delay). Equal: random(delay/2, delay). Full has more variance.
Question
AWS recommended backoff?
Click to reveal answer
Answer
min(max_delay, base * 2^attempt) with full jitter random(0, delay)
Revision Notes
Key Takeaways
- 1.Exponential backoff doubles delay each retry
- 2.Always add jitter to prevent thundering herd
- 3.Set max_delay cap to prevent long waits
- 4.Full jitter (random(0, delay)) is commonly used
- 5.Combine with circuit breaker for resilience
Interview Tips
- •Know the formula: delay = base * 2^attempt
- •Explain why jitter prevents thundering herd
- •Discuss max delay cap importance
- •Give examples of jitter types
Cheat Sheet
Cheat Sheet: Exponential Backoff
Formula
delay = base * 2^attempt
1s → 2s → 4s → 8s → 16s...
Jitter Types
- Full: random(0, delay)
- Equal: random(delay/2, delay)
- Decorrelated: random(base, prev*3)
Why Jitter
- Prevent thundering herd
- Spread retry times
- Smoother load
Implementation
- Set max_delay cap
- Log retry attempts
- Monitor retry rates
- Combine with circuit breaker