Three topics every API review hits
Versioning — how you evolve the API without breaking existing clients.
Pagination — how you return large collections without melting the database or the client.
Idempotency keys — how POST (and some PATCH) survive retries after timeouts without duplicate side effects.
These appear in backend interviews, design docs, and production incidents. Know one coherent policy for each.
Versioning strategies
| Approach | Pros | Cons |
|---|---|---|
URL prefix /v1/orders |
Obvious in logs, browser, curl | Clutters paths; many versions accumulate |
Header Accept: application/vnd.example.v2+json |
Clean URLs | Harder to spot in casual debugging; gateway caching nuance |
| No version / additive only | Simplest for small teams | Requires discipline; breaking changes must be rare |
Convention: never ship a breaking change without a version bump or a documented deprecation window.
What counts as breaking
- Removing or renaming fields clients depend on
- Changing field types (
stringid →number) - Changing status codes or error shapes for the same failure
- Tightening validation so previously accepted payloads fail
What is usually safe (additive)
- New optional fields in responses
- New optional query parameters
- New endpoints
- New enum values (if clients tolerate unknown values — document forward compatibility)
Pick URL or header versioning at org level and stick to it. Mixing both without documentation confuses SDK generators.
Pagination: offset vs cursor
Large collections cannot return in one response. Two dominant patterns:
Offset / limit (?page=2&limit=20 or ?offset=40&limit=20)
Pros: simple; maps directly to SQL OFFSET / LIMIT; easy for clients to jump to “page 5.”
Cons: slow at large offsets — the database still walks skipped rows. Unstable under churn — if rows insert/delete between page fetches, clients see duplicates or gaps.
Use for admin UIs with small datasets, internal tools, or when approximate paging is acceptable.
Cursor (?after=opaque&limit=20)
Pros: stable under concurrent inserts when the cursor encodes an indexed sort key (e.g. (created_at, id) tuple). Efficient for “infinite scroll” feeds.
Cons: opaque cursor requires documented sort order; jumping to arbitrary page numbers is awkward.
Encode cursors as opaque base64 or signed blobs — do not expose raw internal ids unless intentional.
Response shape convention:
{
"items": [ ... ],
"next_cursor": "eyJpZCI6MTAwfQ",
"has_more": true
}
Include total count only if cheap to compute; exact totals on billion-row tables are expensive.
Interview answer: prefer cursor at scale; offset is fine for small or internal surfaces.
Filtering and sorting
Query parameters keep reads cache-friendly when using GET:
GET /orders?status=open&sort=-createdAt&limit=20
Conventions:
- Minus prefix for descending:
sort=-createdAt - Whitelist allowed filter and sort keys — reject unknown params with 400 or document ignore behavior
- Sparse fieldsets (optional):
?fields=id,amountto shrink payloads for mobile
Date ranges: ?created_after=2026-01-01T00:00:00Z — use ISO-8601 and document timezone (UTC recommended).
Idempotency keys
POST is not idempotent. Networks retry. Payment processors and order systems require client-supplied idempotency keys:
POST /payments
Authorization: Bearer ...
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{ "amount": 1999, "currency": "usd", "source": "tok_..." }
Server behavior:
- First request with key K: process payment, store
(K → response, status)with TTL (24–72 hours common). - Duplicate request with same K and same body: return stored response (often replay 201 or 200 with same body).
- Same K, different body: return 409 Conflict — client bug or key reuse error.
Document policy clearly. Stripe and similar APIs are reference implementations interviewers know.
Keys should be unique per logical operation (UUID v4). Clients generate; server enforces.
Also applies to: order creation, ticket booking, account provisioning — any POST where duplicate side effects are unacceptable.
Design review checklist
| Topic | Recommendation |
|---|---|
| Version | URL or header — one org standard |
| Breaking change | New major version + migration period |
| Lists at scale | Cursor on indexed sort key |
| Lists internal/small | Offset acceptable with documented limits |
| Money / create POST | Idempotency-Key required |
| Filters | Whitelist params; ISO dates in UTC |
Interview framing
“Offset vs cursor?” — Offset simple but slow and unstable at scale; cursor follows stable sort, handles churn for feeds.
“Where to put API version?” — URL /v1/ and Accept-header vendor media types both common; pick one standard.
“What is an idempotency key?” — Client token so server recognizes retries of the same logical operation and avoids duplicate charges or creates.
Next: gRPC — when procedure-shaped APIs beat resource-shaped HTTP.