Build a Retry Engine with Exponential Backoff

Difficulty: Easy · Category: Retry Pattern · Languages: Java 17, Python 3.11, Go 1.22, Node.js 20, TypeScript 5.x

Retry Engine with Exponential Backoff

Background

Network calls fail. Databases go down temporarily. External APIs hit rate limits. In production systems, transient failures are the norm, not the exception. A well-designed retry engine handles these gracefully by retrying failed operations with increasing delays (exponential backoff) and randomized jitter to prevent the "thundering herd" problem.

Libraries like Resilience4j, Spring Retry, and Polly all implement this pattern. In this problem you will build your own from scratch.

Your Task

Implement the RetryEngineContract interface. Your implementation must:

  1. Support configurable retry parameters: max retries, initial delay, backoff multiplier, and max delay cap.
  2. Implement exponential backoff: each retry delay is multiplied by the backoff factor, up to the max delay cap.
  3. Add jitter (±10% randomization) to each delay to prevent thundering herd.
  4. Track per-operation state: attempt count, status, last error, and timestamps of each attempt.
  5. Support cancellation and reset of operations.

Recommended infrastructure: kv-store — for tracking retry state and attempt history, but you may use in-memory structures.

How It Works

The platform injects your chosen infrastructure SDK(s) via constructor. For example, if you use the kv-store:

public class MyRetryEngine implements RetryEngineContract {
    private final KVStoreClient kvStore;

    public MyRetryEngine(KVStoreClient kvStore) {
        this.kvStore = kvStore;
    }

    // ... implement methods using kvStore.put(), kvStore.get(), etc.
}

See the SDK Docs tab for the full API reference of your chosen system.

Interface

public interface RetryEngineContract {
    void configure(int maxRetries, long initialDelayMs, double backoffMultiplier, long maxDelayMs);
    boolean execute(String operationId, Callable<Boolean> action);
    int getAttemptCount(String operationId);
    String getStatus(String operationId);
    String getLastError(String operationId);
    long getNextRetryDelayMs(String operationId);
    List<Long> getRetryTimestamps(String operationId);
    void cancel(String operationId);
    void reset(String operationId);
}

Backoff Formula

delay(n) = min(initialDelayMs × multiplier^(n-1), maxDelayMs)
actualDelay = delay(n) × (1 + jitter)   where jitter ∈ [-0.10, +0.10]

Status Lifecycle

PENDING → RETRYING → SUCCEEDED
                   → FAILED
                   → CANCELLED

Constraints

Examples

configure(3, 100, 2.0, 5000)

// Action succeeds on attempt 3:
execute("op-1", () -> attemptCount++ >= 2)  → true
getAttemptCount("op-1")                      → 3
getStatus("op-1")                            → "SUCCEEDED"
getNextRetryDelayMs("op-1")                  → 0  (completed)

// Action always fails:
execute("op-2", () -> { throw new Exception("timeout"); })  → false
getAttemptCount("op-2")                      → 4  (1 + 3 retries)
getStatus("op-2")                            → "FAILED"
getLastError("op-2")                         → "timeout"

Hints

Infrastructure your code runs against

How your solution is graded

Your score combines functional correctness (40%), performance (40%) and code quality (20%).

Practice this problem

Related LLD problems