go 50 lines · 8 steps

Retry with exponential backoff in Go

A retry loop that re-runs an operation with capped exponential backoff, jitter, and context-aware cancellation.

Explained by highlit
1package retry
2 
3import (
4 "context"
5 "errors"
6 "math"
7 "math/rand"
8 "time"
9)
10 
11type Operation func(ctx context.Context) error
12 
13type Config struct {
14 MaxAttempts int
15 BaseDelay time.Duration
16 MaxDelay time.Duration
17}
18 
19func Do(ctx context.Context, cfg Config, op Operation) error {
20 var lastErr error
21 
22 for attempt := 0; attempt < cfg.MaxAttempts; attempt++ {
23 if err := op(ctx); err != nil {
24 lastErr = err
25 if errors.Is(err, context.Canceled) {
26 return err
27 }
28 } else {
29 return nil
30 }
31 
32 if attempt == cfg.MaxAttempts-1 {
33 break
34 }
35 
36 backoff := float64(cfg.BaseDelay) * math.Pow(2, float64(attempt))
37 if backoff > float64(cfg.MaxDelay) {
38 backoff = float64(cfg.MaxDelay)
39 }
40 jitter := time.Duration(rand.Int63n(int64(backoff)))
41 
42 select {
43 case <-time.After(jitter):
44 case <-ctx.Done():
45 return ctx.Err()
46 }
47 }
48 
49 return lastErr
50}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Exponential backoff spaces out retries so a struggling dependency gets room to recover instead of being hammered.
  2. 2Adding jitter and a max-delay cap prevents synchronized retry storms and unbounded waits.
  3. 3Threading context through every retry lets cancellation interrupt both the operation and the sleep between attempts.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Retry with exponential backoff in Go — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code