Caching weather API responses is one of the first things developers reach for when they start worrying about rate limits or response latency. It’s the right instinct. But the naive implementation — slap a TTL of 10 minutes on every response, move on — quietly fails in ways that are hard to debug after the fact.
The failure isn’t that caching is wrong. It’s that weather data doesn’t age uniformly, and a flat TTL doesn’t know that.
Why Flat TTLs Are the Wrong Default
A current conditions response and a 10-day forecast response don’t have the same staleness characteristics. Current conditions — temperature, wind, METAR-derived sky state — can change meaningfully in 20 minutes during a front passage or a convective afternoon. A day-10 forecast is already operating in a regime where the underlying NWP model isn’t going to produce dramatically different output if you cached it 45 minutes ago. Treating both with a 10-minute TTL either over-refreshes the forecast or under-refreshes current conditions, depending on which way you’ve tuned it.
There’s also a less obvious issue: model update cadence. HRRR runs hourly and covers CONUS at 3km resolution — it’s the model you actually want for same-day short-range accuracy, especially for convective events. Caching an hourly-updating product for 55 minutes is fine. Caching it for 3 hours means you’re potentially serving a forecast that’s missed two full model cycles. GFS runs every 6 hours, so the calculus is different there. If you’re applying the same TTL across the board without knowing which model is backing the response, you’re guessing.
A Tiered TTL Structure That Actually Makes Sense
The approach we use is differentiating cache TTL by both the type of endpoint and the forecast horizon within that endpoint. Roughly:
- Current conditions: 5–10 minutes maximum. This is the one place where staleness genuinely matters most and the data genuinely changes fastest.
- Hourly forecast, next 12 hours: 20–30 minutes. The underlying HRRR output is hourly, so there’s no reason to refresh more often than that, but you do want to pick up the next run cycle reasonably quickly.
- Daily forecast, days 1–3: 60–90 minutes. NAM and HRRR are the primary models here; a one-run lag is unlikely to change the headline numbers materially for most use cases.
- Daily forecast, days 4–10: 3–6 hours. GFS and ECMWF dominate at this horizon. The ensemble spread is already large enough that an extra couple of hours of cache age barely registers against model uncertainty.
These aren’t hard rules — they’re the shape of a reasonable policy. The exact numbers depend on your application’s tolerance for staleness versus your API call budget.
The Specific Moments When Cache Should Break Early
Flat TTLs also miss event-driven invalidation. Some situations make cached data actively misleading rather than just slightly stale.
Active weather alerts. If there’s a tornado warning or a severe thunderstorm warning for a location, serving a cached “partly cloudy, 18°C” from 8 minutes ago isn’t just wrong — it’s the kind of wrong that creates real problems for applications in the safety, outdoor operations, or event management space. If your application uses the alerts endpoint, that endpoint should have its own short TTL (2–3 minutes), independently from the forecast cache. Don’t bundle it in.
Rapid condition changes. This one is harder to detect in advance, but one pattern that helps: if you’re storing the previous response alongside the cached one, you can compare a few key fields on cache miss — if temp_c has moved more than 5°C or wind_kph more than 20 km/h since the last fetch, invalidate the forecast cache for that location too, even if it hasn’t hit its TTL. It’s a rough heuristic, but it catches most convective events and front passages where stale forecasts become a real problem.
Cache Key Design Matters More Than People Realize
A bad cache key structure is a whole separate failure mode. The most common one: keying off the raw user-supplied location string instead of a normalized identifier.
"london", "London", "London, UK", and "51.51,-0.13" might all resolve to the same forecast internally, but if your cache treats them as different keys, you’re multiplying your API calls by however many variations your users pass in. Normalize the key. If you’re resolving to a coordinate pair first, use the rounded coordinate (e.g. 2 decimal places, roughly 1km resolution) as the canonical cache key — not the user’s original input string.
The other common mistake: not including the language or unit preference in the key. A cached response in metric units will silently be wrong for a user requesting imperial. This is the kind of bug that surfaces in production right after you’ve added multi-region support and starts generating very confusing support tickets.
Where We’ve Landed on This Internally
Our own infrastructure uses Redis (specifically KeyDB, a multithreaded Redis fork) for response caching across the API layer. The TTL strategy is tiered by endpoint type rather than a blanket value, and the alerts path is intentionally kept off the main forecast cache entirely. We run on a self-managed fleet on UpCloud rather than managed cloud, which means we’re directly aware of what happens when a Redis node misbehaves under write pressure — a flat-TTL strategy that hammers the same keys on expiry creates thundering herd patterns that a tiered approach naturally spreads out.
That’s worth thinking about if you’re running your own caching layer rather than relying on an edge CDN: expiry jitter matters. Don’t let 10,000 cached entries for the same city expire at exactly the same second. Add random jitter of 10–20% to your TTLs so the re-fetch load distributes instead of spiking.
One Caveat Worth Being Honest About
None of this eliminates the fundamental tension between cache efficiency and data freshness. If your use case genuinely requires the most current observation available — a safety-critical outdoor operations dashboard, or an application that triggers physical actions based on weather state — aggressive caching is the wrong architecture entirely. You should be looking at shorter polling intervals or a push-based delivery model instead. Caching is the right tool for reducing redundant load on steady-state usage. It’s not the right tool for applications where a 15-minute lag in detecting a wind gust threshold has real consequences.
The test is simple: what’s the worst realistic outcome if a user sees data that’s 20 minutes old? If the answer is “a mildly suboptimal UX,” cache aggressively. If the answer involves someone making a decision they shouldn’t, keep the TTL short and budget for the extra calls.
