go-retry

A dependency-free Go library for retrying flakey operations with pluggable backoff and jitter middleware.

Library
Go
vv0.4.0
717stars
Apache License 2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
61/100Good
Development Activity68
Maintenance44
Community44
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
71/100Good
Architecture85
Code Quality90
Innovation45
Learning Curve65

go-retry gives Go developers a small, composable toolkit for retrying operations that may fail transiently, such as database connections or network calls. Instead of a single opinionated retry loop, it exposes a Backoff interface and a handful of constructors (NewConstant, NewExponential, NewFibonacci) that callers wrap with middleware to add jitter, cap individual delays, limit total retries, or bound total elapsed time.

Callers explicitly mark which errors should trigger a retry with RetryableError, so a function’s own logic decides what is transient versus fatal, and everything respects Go’s context.Context for cancellation. The library has zero dependencies beyond the standard library, so it adds no bloat and no supply-chain surface to a project that just needs a retry loop.

What You Get

  • Three built-in backoff algorithms — constant, exponential, and Fibonacci — each created with a single constructor call
  • Composable middleware (WithJitter, WithJitterPercent, WithFullJitter, WithMaxRetries, WithCappedDuration, WithMaxDuration) that wraps any Backoff to change its behavior
  • Generic DoValue[T] and Do functions that run a retryable function against a Backoff and a context.Context, stopping on cancellation or a non-retryable error
  • Explicit opt-in retry semantics via RetryableError, so only errors a caller marks are retried and everything else fails fast
  • Safe-for-concurrent-use backoff implementations built on sync/atomic, so a single Backoff value can be shared across goroutines

Common Use Cases

  • Retrying a database connection or ping until it succeeds or the context is canceled
  • Wrapping outbound HTTP calls to a flakey upstream API with exponential backoff and jitter to avoid thundering-herd retries
  • Bounding the total time spent retrying a startup health check with WithMaxDuration before failing the process
  • Building a custom backoff policy by writing your own BackoffFunc and layering the existing middleware on top of it

Under The Hood

Architecture The whole module is a single flat retry package with no subdirectories. Its core abstraction is the Backoff interface (Next() (time.Duration, bool)), implemented by small concrete types (exponentialBackoff, fibonacciBackoff) and by a BackoffFunc adapter modeled directly on Go’s http.HandlerFunc pattern. Retry execution lives in retry.go’s generic DoValue[T]/Do, which loop until the context is canceled, a non-retryable error surfaces, or the backoff itself signals stop, waiting via a time.Timer selected against ctx.Done(). Middleware in backoff.go (WithJitter, WithMaxRetries, WithCappedDuration, and friends) wraps any Backoff and returns a new closure-based BackoffFunc, composing behavior through decoration rather than inheritance. Because every concrete backoff and every middleware function depends on nothing but the single-method Backoff interface, changing that interface’s shape would break every implementation and every middleware wrapper simultaneously.

Tech Stack Built for Go 1.25 with zero external dependencies — go.mod has no require lines, and the code imports only stdlib packages (context, errors, math, math/rand/v2, sync, sync/atomic, time). Tests run via the standard testing package using t.Parallel() and godoc-verified Example functions rather than a third-party assertion library. CI is a single GitHub Actions workflow (.github/workflows/test.yml) that runs go test -race -short -timeout=5m ./... on every push and pull request to main, pinning actions to ratcheted commit SHAs. Distribution is the standard Go module proxy under github.com/sethvargo/go-retry; there is no separate build step beyond go build.

Code Quality Every source file has a matching test file (backoff_constant_test.go, backoff_exponential_test.go, backoff_fibonacci_test.go, backoff_test.go, retry_test.go), and CI runs the full suite with the race detector enabled. Errors are typed via an unexported retryableError struct implementing Unwrap/Error, checked with errors.As rather than sentinel comparisons, so only errors explicitly wrapped by a caller are retried. Naming is idiomatic Go throughout (exported NewX constructors, unexported state), and the concurrency-sensitive backoffs use atomic.Uint64/atomic.Pointer with compare-and-swap loops instead of locks wherever that pattern applies, falling back to a sync.Mutex only in WithMaxRetries where a CAS loop would be awkward. No linter configuration file is present in the repo, but the style is consistent and comment coverage on exported identifiers is thorough.

What Makes It Unique The retry/backoff pattern itself is well-established — the project’s own README benchmarks itself directly against two other popular Go backoff libraries. What distinguishes this implementation is the http.Handler-style BackoffFunc adapter, which lets any function satisfy Backoff and lets callers layer arbitrary custom middleware on top of the built-in ones without a plugin system, plus a documented notes-and-caveats section calling out non-obvious behavior like modifier ordering effects. It is a clean, minimal-dependency, benchmark-conscious take on a standard pattern rather than a conceptually new one.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search