All Snippets Snippet PHP

Retry with Exponential Backoff

1s 2s 4s 8s Fail Fail Fail Pass

Automatically retry failed operations with increasing delays and jitter to prevent thundering herd problems.

D
Kunwar "AKA" AJ Sharing what I have learned
Feb 20, 2026 1 min PHP

The Pattern

retryWithBackoff.php
function retryWithBackoff(
    callable $operation,
    int $maxAttempts = 3,
    int $baseDelayMs = 100
): mixed {
    $lastException = null;

    for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
        try {
            return $operation();
        } catch (Throwable $e) {
            $lastException = $e;

            if ($attempt === $maxAttempts) {
                break;
            }

            // Exponential backoff: 100ms, 200ms, 400ms...
            $delayMs = $baseDelayMs * (2 ** ($attempt - 1));

            // Add jitter (±25%) to prevent thundering herd
            $jitter = $delayMs * 0.25;
            $delayMs += random_int((int) -$jitter, (int) $jitter);

            usleep($delayMs * 1000);
        }
    }

    throw $lastException;
}

What Happens Under the Hood

Let us trace what happens when an API call fails twice then succeeds on the third attempt:

execution-flow.txt
Attempt 1: Call API → Fails (timeout)
  → Wait ~100ms (base delay)

Attempt 2: Call API → Fails (server error)
  → Wait ~200ms (doubled)

Attempt 3: Call API → Succeeds
  → Return result immediately

Total wait: ~300ms
Without retry: immediate failure, manual intervention needed

The jitter is critical. Without it, if 100 clients all fail at the same time, they all retry at the same time — overwhelming the server again. Jitter spreads the retries randomly across a time window, giving the server breathing room to recover.

Why This Matters

failure-recovery.txt
Without retry:
  API hiccup → Pipeline fails → Manual restart → Data delayed

With retry + backoff:
  API hiccup → Auto-retry in 100ms → Succeeds → Pipeline continues
  Transient failures become invisible to the user

Usage Example

usage.php
// Retry an API call up to 3 times with exponential backoff
$response = retryWithBackoff(
    fn() => $httpClient->post('/api/data', $payload),
    maxAttempts: 3,
    baseDelayMs: 200
);

// Retry a database connection
$pdo = retryWithBackoff(
    fn() => new PDO($dsn, $user, $pass),
    maxAttempts: 5,
    baseDelayMs: 500
);

Use this for any operation that can fail transiently: API calls, database connections, file locks, external service requests. Do not use it for operations that fail deterministically — retrying bad input three times will not make it valid.