backoff

backoff computes successive retry delays with exponential growth, a bounded maximum, and random jitter.

Part of nurago, a collection of independent Go packages for backend services.

import "github.com/tecnickcom/nurago/pkg/backoff"

Package backoff computes successive retry delays with exponential growth, a bounded maximum, and random jitter.

Schedule is a per-call delay calculator: given a base delay, a growth factor, a jitter ceiling, and a maximum, each call to Schedule.Next returns the next delay and advances the progression. It performs no timing, scheduling, goroutines, or I/O; callers own their timers and loops and ask the Schedule for the next duration.

AddJitter exposes the jitter step on its own for callers that pace work at a fixed interval rather than backing off exponentially.

Jitter strategies

By default a Schedule adds a fixed random ceiling (JitterAdditive); its desynchronizing effect shrinks as the delay grows. JitterFull and JitterEqual instead scale the jitter with the delay, decorrelating concurrent clients at large delays. Select one via Config.Strategy.

Bounds and overflow

Both the internal progression and the jitter addition are clamped so no delay can overflow into a negative duration, regardless of factor, attempt count, or configured maximum. The growth is capped below the int64 limit (~146 years), and the jitter addition saturates at math.MaxInt64 rather than wrapping. Negative or NaN inputs are floored to zero.

Delays are computed in float64, so integer-nanosecond precision is exact only up to ~2^53 ns (~104 days); beyond that a result may differ by a few nanoseconds.

Usage

s := backoff.New(backoff.Config{
    Base:     100 * time.Millisecond,
    Factor:   2,
    Jitter:   50 * time.Millisecond,
    MaxDelay: 30 * time.Second,
})

for {
    // ... attempt work ...
    time.Sleep(s.Next()) // 100ms, 200ms, 400ms, ... capped at 30s, each +[0,50ms)
}

Concurrency

A Schedule is stateful and must not be used concurrently; construct one per retry sequence. AddJitter is a stateless function and is safe for concurrent use.

When To Use

  • You already have a retry loop and only need the delay schedule.
  • Concurrent clients retry against the same dependency and must not synchronize into a thundering herd.
  • You want the jitter strategy to be an explicit choice rather than a hidden default.

Example

s := backoff.New(backoff.Config{
	Base:     100 * time.Millisecond,
	Factor:   2,
	Jitter:   0, // disabled so the example output is deterministic
	MaxDelay: 350 * time.Millisecond,
})

for range 4 {
	fmt.Println(s.Next())
}

// Output:
// 100ms
// 200ms
// 350ms
// 350ms

Full source is in example_backoff_test.go. More runnable examples are on pkg.go.dev.

Dependencies

This package reaches no external module: it uses only the Go standard library.