API design
REST vs GraphQL vs gRPC, pagination, rate limits
The problem
The API is the contract between client and server — and between your services. Good contracts are easy to use, cache, and evolve; bad ones create coupling and backward-compatibility nightmares. This block is about choosing the contract's shape on purpose.
The three styles
REST: resources as URLs (/users/123), HTTP verbs as operations, stateless requests. Simple, cacheable (the URL is the cache key), universal. Its twin pains: over-fetching (each call returns too much) and under-fetching (each call returns too little, so one page needs three round trips — brutal on slow connections). GraphQL: one endpoint, the client sends the exact shape it wants, one round trip, zero excess — at the cost of URL-based caching and having to defend against arbitrarily expensive queries. gRPC: binary protobuf, typed contracts, streaming, very fast, not browser-native. The rule of thumb: REST for public APIs, gRPC for internal service-to-service, GraphQL when diverse clients need diverse shapes.
Pagination — offset vs cursor
?page=500&limit=20 makes the database walk and discard 10,000 rows before returning 20 — OFFSET is a scan. Cursor pagination returns an opaque token encoding the last-seen sort key; the next request says "20 after this key," which the B-tree index seeks directly — same speed at page 1 or page 50,000. Offset for shallow jumpable pages; cursors for infinite scroll.
Rate limiting
Every public API needs a bouncer or one bad client degrades everyone. The two algorithms worth naming: token bucket (tokens drip in at a rate, requests spend them — allows bursts) and sliding window (count requests in the trailing interval — smooth). Reject with 429 Too Many Requests plus a Retry-After header. Apply per user, IP, or API key.
Versioning and idempotency
Breaking changes crash clients: version in the URL (/v1/users) — explicit beats clever. GET, PUT, and DELETE are idempotent by nature; POST is not — so critical creates (payments) carry an idempotency key so retries can’t double-charge. Define your API early in a design interview: "clients call GET /feed?cursor=X&limit=20, writes go to POST /posts" — it shows you think in contracts.