Backoff policies determine the wait duration between retry attempts. The package provides several built-in policies and allows custom implementations through the BackOff interface.
// BackOff is a backoff policy for retrying an operation
type BackOff interface {
// NextBackOff returns the duration to wait before retrying the operation,
// or backoff.Stop to indicate that no more retries should be made
NextBackOff() time.Duration
// Reset to initial state
Reset()
}ExponentialBackOff increases the backoff period exponentially for each retry attempt using randomization to add jitter. This is the most commonly used backoff strategy.
type ExponentialBackOff struct {
// Initial interval between retries
InitialInterval time.Duration
// Randomization factor (0.0 to 1.0) to add jitter to intervals
RandomizationFactor float64
// Multiplier for interval increase after each retry
Multiplier float64
// Maximum interval between retries (caps exponential growth)
MaxInterval time.Duration
// Maximum total elapsed time (0 = never stop based on time)
// After this time, NextBackOff() returns Stop
MaxElapsedTime time.Duration
// Duration returned when stopping
Stop time.Duration
// Clock interface for time measurement
Clock Clock
}// Create ExponentialBackOff with default values and optional configuration
func NewExponentialBackOff(opts ...ExponentialBackOffOpts) *ExponentialBackOff// Option function type for configuring ExponentialBackOff
type ExponentialBackOffOpts func(*ExponentialBackOff)
// Set initial interval between retries
func WithInitialInterval(duration time.Duration) ExponentialBackOffOpts
// Set randomization factor to add jitter to intervals
func WithRandomizationFactor(randomizationFactor float64) ExponentialBackOffOpts
// Set multiplier for increasing interval after each retry
func WithMultiplier(multiplier float64) ExponentialBackOffOpts
// Set maximum interval between retries
func WithMaxInterval(duration time.Duration) ExponentialBackOffOpts
// Set maximum total time for retries
func WithMaxElapsedTime(duration time.Duration) ExponentialBackOffOpts
// Set duration returned when stopping
func WithRetryStopDuration(duration time.Duration) ExponentialBackOffOpts
// Set custom clock for time measurement
func WithClockProvider(clock Clock) ExponentialBackOffOpts// Calculate next backoff interval using exponential formula with jitter
func (b *ExponentialBackOff) NextBackOff() time.Duration
// Reset interval to initial value and restart timer
func (b *ExponentialBackOff) Reset()
// Get elapsed time since creation or last reset
// Thread-safe: Can be called even while the backoff policy is used by a running ticker
func (b *ExponentialBackOff) GetElapsedTime() time.Durationconst (
DefaultInitialInterval = 500 * time.Millisecond
DefaultRandomizationFactor = 0.5
DefaultMultiplier = 1.5
DefaultMaxInterval = 60 * time.Second
DefaultMaxElapsedTime = 15 * time.Minute
)import (
"time"
"github.com/cenkalti/backoff/v4"
)
// Using default configuration
b := backoff.NewExponentialBackOff()
// Custom configuration
b := backoff.NewExponentialBackOff(
backoff.WithInitialInterval(1*time.Second),
backoff.WithMaxInterval(30*time.Second),
backoff.WithMaxElapsedTime(5*time.Minute),
backoff.WithMultiplier(2.0),
backoff.WithRandomizationFactor(0.3),
)
operation := func() error {
// Your operation
return nil
}
err := backoff.Retry(operation, b)NextBackOff() is calculated using the following formula:
randomized interval = RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor])For example, with RetryInterval = 2, RandomizationFactor = 0.5, and Multiplier = 2:
Note: MaxInterval caps the RetryInterval (before randomization), not the final randomized interval.
Example sequence with default arguments over 10 tries:
| Request | RetryInterval | Randomized Interval Range |
|---|---|---|
| 1 | 0.5s | [0.25s, 0.75s] |
| 2 | 0.75s | [0.375s, 1.125s] |
| 3 | 1.125s | [0.562s, 1.687s] |
| 4 | 1.687s | [0.843s, 2.53s] |
| 5 | 2.53s | [1.265s, 3.795s] |
| 6 | 3.795s | [1.897s, 5.692s] |
| 7 | 5.692s | [2.846s, 8.538s] |
| 8 | 8.538s | [4.269s, 12.807s] |
| 9 | 12.807s | [6.403s, 19.210s] |
| 10 | 19.210s | backoff.Stop |
ConstantBackOff always returns the same backoff delay, useful when you want fixed intervals between retries.
type ConstantBackOff struct {
// Interval to return for each retry
Interval time.Duration
}
// Create constant backoff with specified interval
func NewConstantBackOff(d time.Duration) *ConstantBackOff
// Returns constant interval
func (b *ConstantBackOff) NextBackOff() time.Duration
// No-op for constant backoff
func (b *ConstantBackOff) Reset()import (
"time"
"github.com/cenkalti/backoff/v4"
)
// Retry every 5 seconds
b := backoff.NewConstantBackOff(5 * time.Second)
err := backoff.Retry(operation, b)ZeroBackOff always returns zero duration, causing immediate retries without any delay.
type ZeroBackOff struct{}
// Always returns 0 (immediate retry)
func (b *ZeroBackOff) NextBackOff() time.Duration
// No-op
func (b *ZeroBackOff) Reset()import "github.com/cenkalti/backoff/v4"
// Retry immediately without delay
b := &backoff.ZeroBackOff{}
err := backoff.Retry(operation, b)Warning: ZeroBackOff will retry indefinitely without delay. Always combine with context timeout or WithMaxRetries to prevent infinite loops.
StopBackOff always returns the Stop constant, meaning the operation should never be retried.
type StopBackOff struct{}
// Always returns backoff.Stop (never retry)
func (b *StopBackOff) NextBackOff() time.Duration
// No-op
func (b *StopBackOff) Reset()// Constant indicating no more retries should be made
const Stop time.Duration = -1import "github.com/cenkalti/backoff/v4"
// Execute once, never retry
b := &backoff.StopBackOff{}
err := backoff.Retry(operation, b)Wrap any backoff to stop when context is canceled.
// BackOff with context cancellation support
// This interface embeds BackOff, so it includes NextBackOff() and Reset() methods
// plus the Context() method for accessing the associated context
type BackOffContext interface {
BackOff
Context() context.Context
}
// Wrap backoff to stop on context cancellation
// Panics if ctx is nil
// If b is already a BackOffContext, it unwraps and rewraps with the new context
// to prevent nested wrapping
func WithContext(b BackOff, ctx context.Context) BackOffContextimport (
"context"
"time"
"github.com/cenkalti/backoff/v4"
)
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
b := backoff.WithContext(
backoff.NewExponentialBackOff(),
ctx,
)
err := backoff.Retry(operation, b)
// Will stop retrying when context times out or is canceledLimit the number of retry attempts.
// Wrap backoff to limit maximum retry attempts
// Returns Stop if NextBackOff() has been called too many times
// since the last Reset()
func WithMaxRetries(b BackOff, max uint64) BackOffimport "github.com/cenkalti/backoff/v4"
// Retry at most 3 times
b := backoff.WithMaxRetries(
backoff.NewExponentialBackOff(),
3,
)
err := backoff.Retry(operation, b)
// Will stop after 3 retry attemptsNote: Implementation is not thread-safe.
Custom clock interface for time measurement, useful for testing.
// Clock is an interface that returns current time for BackOff
type Clock interface {
Now() time.Time
}
// Default clock implementation using time.Now()
var SystemClock = systemClock{}import "github.com/cenkalti/backoff/v4"
// Use custom clock for testing
type MockClock struct {
currentTime time.Time
}
func (c *MockClock) Now() time.Time {
return c.currentTime
}
mockClock := &MockClock{currentTime: time.Now()}
b := backoff.NewExponentialBackOff(
backoff.WithClockProvider(mockClock),
)