A Go weather-dashboard API demonstrated by fanning out live weather fetches for a user's saved cities in parallel -- bounded retry-with-backoff against a genuinely flaky upstream, a TTL cache with stale-data fallback, and safe, mutex-free concurrent writes are the central engineering problems, not an afterthought.
Nobody has started this yet — be first.
A single timeout permanently fails a weather fetch instead of retrying
The entire point of this project's retry/backoff logic is to absorb the kind of brief hiccup a real external API has constantly -- and a plain timeout is the single most common kind of hiccup. Right now, a timeout is treated as a permanent, non-retryable failure: one slow response from the upstream weather API and that city instantly shows an error (or falls back to possibly-stale cache) with zero retry attempts, even though the retry mechanism exists and is fully configured to handle exactly this case for every other kind of transient failure.
About this project
A Go weather-dashboard API: users save a list of cities (scoped per-user via an X-User-ID header, Postgres-backed), and GET /dashboard fetches live current weather for every saved city concurrently, cached with a TTL. A bundled local mock provider (cmd/mockprovider) speaks the same request/response shape as the real OpenWeatherMap current-weather endpoint -- and adds a control API for injecting specific failure modes (timeout, 5xx, 429, 404, 401) on demand -- so the entire project builds, tests, and runs end-to-end with no real API key; swapping WEATHER_API_BASE_URL and WEATHER_API_KEY for the real provider is a two-env-var change, no code changes.
Three engineering problems sit at the center, not the edges. First, a dedicated *http.Client with an explicit per-attempt timeout and bounded exponential backoff-with-jitter retries transient failures (timeout, 5xx, connection error, 429) while failing non-transient ones (401, 404, malformed body) immediately -- retrying a bad API key or an unknown city can never succeed. Second, a TTL cache (internal/weather/cache.go) keyed by city name serves an unexpired entry with no upstream call at all, and on an upstream failure falls back to a stale-but-present entry instead of failing the whole request -- a failed refresh never evicts the previous entry, which is what makes the fallback possible. Third, GET /dashboard fans out across a user's saved cities concurrently via golang.org/x/sync/errgroup with a bounded worker pool (DASHBOARD_MAX_CONCURRENCY), where each goroutine writes only to its own pre-assigned index of a pre-sized results slice -- race-free without a mutex or channel, since no two goroutines ever touch the same slot -- and always reports its own success/failure into that slot rather than letting errgroup cancel every other in-flight city on the first error.
Retries stop one attempt short of the configured count
MaxRetries is a deployment-tunable knob (via WEATHER_MAX_RETRIES) that operators use to decide how hard to fight for a successful upstream response before giving up and falling back to stale/error. Right now the client silently gives up one attempt earlier than configured -- with MaxRetries: 2 (documented as "1 initial + 2 retries = 3 total attempts"), it only ever makes 2 attempts. In production this quietly makes the service less resilient to transient upstream hiccups than its own configuration promises, with nothing in the logs or metrics to reveal the discrepancy.
Ten tickets are deliberately injected into an otherwise working, tested Go codebase: some are one-line logic bugs (a missing arm in an OR-chain, an off-by-one loop bound, a subtracted-in-the-wrong-order duration that makes cached data never expire), others are the kind of concurrency and correctness bugs that only a deterministic or race-detector-driven test can reliably catch (a nil-pointer panic on every upstream failure, a genuine data race on a shared results slice under -race), and the rest are straightforward CRUD/HTTP enhancements with the surrounding plumbing already wired up, missing only the one method or query that makes them work. Every ticket has a dedicated Go test in practicetickets/ that goes green when the fix is correct.
WEATHER_API_BASE_URL=https://api.openweathermap.orgWEATHER_API_KEYdocker-compose.prod.yml