retry-go

A lightweight Go library for retrying fallible functions with configurable backoff, jitter, and per-error attempt limits.

Library
Go
vv4.7.0
2,947stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
60/100Good
Development Activity44
Maintenance32
Community64
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
79/100Good
Architecture78
Code Quality85
Innovation62
Learning Curve90

retry-go gives Go developers a simple, composable way to retry an operation that might fail transiently — an HTTP call, a database write, a queue publish — without hand-rolling a loop, a timer, and error-accumulation logic every time. The core API is two functions, Do for functions returning only an error and DoWithData for generic functions that also return a typed value, both driven by a small set of functional options.

Underneath, retry-go tracks every failed attempt and returns them as a combined Error (a []error with Is/As/Unwrap support), so callers can inspect the full failure history or just the last error via LastErrorOnly. Retry behavior is fully pluggable: attempt counts (including unlimited, or per-specific-error caps via AttemptsForError), delay strategies (fixed, exponential backoff, random jitter, or combinations), context cancellation, and a custom RetryIf predicate for deciding which errors are worth retrying at all. Wrapping an error in retry.Unrecoverable short-circuits retries immediately, which is a common pattern for distinguishing “retryable” from “fatal” failures in application code.

The library has no runtime dependencies beyond the Go standard library (only testify is pulled in for tests), making it a safe, low-friction addition to any Go project that talks to a flaky external system.

What You Get

  • Do(fn, opts...) for retrying a plain func() error, and generic DoWithData(fn, opts...) for functions that also return a typed value on success
  • Configurable delay strategies — FixedDelay, BackOffDelay (exponential), RandomDelay (jitter), FullJitterBackoffDelay, and CombineDelay to compose several together
  • Attempt control via Attempts(n), UntilSucceeded() for unbounded retry, and AttemptsForError(n, err) to cap retries for one specific error independently of the overall limit
  • Unrecoverable(err) / IsRecoverable(err) to mark an error as non-retryable and stop the loop immediately, plus a custom RetryIf predicate for arbitrary retry-worthiness logic
  • Context support via Context(ctx) so retries respect cancellation and deadlines, with WrapContextErrorWithLastError to preserve the last function error alongside the context error
  • A combined Error type ([]error) implementing Is, As, and Unwrap for standard-library-compatible error inspection, or LastErrorOnly(true) to surface just the final failure

Common Use Cases

  • Retrying outbound HTTP requests to a flaky or rate-limited third-party API with exponential backoff and jitter to avoid thundering-herd retries
  • Retrying database writes or queue publishes that occasionally fail on transient connection errors, while treating validation errors as unrecoverable via Unrecoverable
  • Wrapping health-check or readiness-probe polling loops with UntilSucceeded() bounded by a Context deadline
  • Building resilient CLI tools or background workers that need bounded retry budgets per error type via AttemptsForError
  • Testing retry behavior deterministically by swapping in a custom Timer via WithTimer instead of waiting on real time.After delays

Under The Hood

Architecture The library is intentionally small and centers on two exported entry points in retry.go: Do, which adapts a plain func() error into the generic path, and DoWithData[T], which holds the actual retry loop. Each call builds a *Config from newDefaultRetryConfig() and applies the caller’s Option functions (from options.go) before entering one of two loops — an unbounded loop when Attempts(0)/UntilSucceeded() is set, or a bounded for loop with a shouldRetry label otherwise. Delay computation, error accumulation into the Error slice type, and context-cancellation handling are all inline in this loop rather than split into separate collaborators, keeping the control flow traceable in one place. Swapping BackOffDelay for FullJitterBackoffDelay or composing multiples via CombineDelay doesn’t touch the loop at all, since every delay strategy is just a DelayTypeFunc(n, err, config) time.Duration — the loop only ever calls one function pointer through config.delayType.

Tech Stack Written in modern Go (go 1.20 in go.mod) and using generics (RetryableFuncWithData[T any], DoWithData[T any]) to support typed return values without reflection or interface{} casts. The only non-test dependency is the standard library; testify is a test-only dependency. There’s no build tooling beyond a Makefile wrapping go test, go vet, and golangci-lint, and no external services or runtime configuration — it’s a pure, importable API surface.

Code Quality The repo has an extensive test suite (retry_test.go, last_error_test.go, plus scenario tests under examples/ for HTTP GETs, delay-based-on-error, and error-history handling) using testify/assert for expressive assertions, and CI runs the matrix across multiple Go versions and operating systems with golangci-lint as a separate required job. Error handling is explicit and typed throughout — the custom Error type implements Is/As/Unwrap to integrate cleanly with the standard errors package rather than relying on string matching, and the unrecoverableError wrapper type gives callers a structured way to opt out of retries.

What Makes It Unique What distinguishes retry-go from more generic backoff libraries is its combination of per-error attempt budgets (AttemptsForError) alongside the overall attempt limit, its accumulation of the full multi-attempt error history into a single inspectable Error value (rather than only the last error), and first-class generics support for functions that return data on success — most comparable Go retry libraries predate generics and only support the plain func() error signature.

Used by 5 apps in this directory

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