Reference · Performance & Evolution
Caching, Pagination & Versioning
Published
The compressed essence for quick pre-interview review. Three things that decide whether an API stays fast and stays alive as it grows.
Caching & conditional requests
Cacheability is a core REST constraint: responses label themselves, intermediaries reuse them, round-trips vanish. MDN · RFC 9110.
| Cache-Control directive | Meaning |
|---|---|
max-age=N | Fresh for N seconds; serve without revalidating. |
s-maxage=N | Freshness for shared caches (CDN/proxy); overrides max-age there. |
public | Any cache, including shared, may store it. |
private | Only the end-user’s browser may store it — never a shared cache. |
no-store | Never store anywhere. For sensitive data. |
no-cache | May store, but must revalidate with the origin before each reuse. |
must-revalidate | Once stale, must revalidate — no serving stale on error. |
stale-while-revalidate=N | Serve stale instantly for up to Ns while refreshing in the background. |
Expires is the legacy absolute-date equivalent of max-age; Cache-Control wins where both appear.
- Validators — freshness ran out, is it still good?
ETagis an opaque version tag (strong, or weakW/”…”). Client echoes it inIf-None-Match; if unchanged the server returns304 Not Modifiedwith no body — the cheap win.Last-Modified+If-Modified-Sinceis the timestamp-based fallback.- Conditional writes — optimistic concurrency
GEThands back anETag→ client sendsIf-Match: ”…”onPUT/PATCH→ server returns412 Precondition Failedif the resource changed underneath. Prevents lost updates without locking.If-None-Match: *= create only if it doesn’t already exist.- Vary — partition the cache key
Varytells caches which request headers change the response (Accept,Authorization). Pitfall: authenticated responses must beprivate/no-storeorVary: Authorization— otherwise a shared cache serves one user’s data to another.
Pagination
Two strategies, one decision. Pick by access pattern, not habit. GitHub · Stripe.
| Offset / limit | Cursor / keyset | |
|---|---|---|
| Deep-page cost | O(offset) scan-and-discard | flat, index-backed |
| Consistency under writes | dups & skips as rows shift | stable |
| Jump-to-page | yes | no |
| Exact total | easy | expensive |
| Best for | human page UIs | large streaming feeds |
- Cursor query pattern
WHERE (sort_key, id) > last ORDER BY sort_key, id LIMIT n. Hand back an opaque cursor (base64-encoded) plushas_more. The tuple comparison breaks ties onidso no row is skipped or repeated. Keep sort + filter stable for a cursor’s lifetime, or it points at nothing meaningful.- Totals
- Exact counts get expensive at scale — omit or approximate them rather than scan the table on every page.
- Exemplars
- GitHub:
?page=+ aLinkheader (rel=“next”/“prev”/“last”), andbefore/aftercursors. Stripe:starting_after/ending_before+has_more.
Versioning & evolution
The skill is changing an API thousands of clients depend on without breaking them. RFC 8594 (Sunset).
| Breaking — needs a new version | Non-breaking — ship freely |
|---|---|
| Remove or rename a field | Add an optional request field |
| Change a field’s type or meaning | Add a response field |
| Make an optional field required | Add a new endpoint |
| Tighten validation; change defaults | Add an enum value (if clients tolerate unknowns) |
| Remove an endpoint or enum value |
| Where to put the version | Pros | Cons |
|---|---|---|
URI path /v1/… | Visible; trivial to route, test, cache | Couples version to URL; “not pure REST” |
Custom header API-Version | Clean, stable URLs | Invisible; easy to forget; hard to test in a browser |
Media type application/vnd.x+json | Most RESTful; HATEOAS-friendly | Complex; poor tooling support |
- Senior rules
- Prefer additive evolution + the tolerant reader (clients ignore unknown fields). Cut a version only on real breakage. Expose only major versions. Pin existing clients — Stripe pins a version per account; GitHub uses a date-based version header.
- Deprecation lifecycle
- announce → parallel-run old & new → emit
Deprecation+Sunsetheaders (RFC 8594) → publish a migration guide with a long window → monitor real usage → remove.