🔒
← System Design

#2 API Design

Last reviewed 2026-08-10

What it is

The contract you ship outlives your code

An API is the contract between client and server — how the outside world asks your service to do work. Once v1 ships it's far harder to change than the implementation behind it; your API decisions outlive most of your code.

Three protocols dominate: REST, GraphQL, gRPC — each trades developer experience, network efficiency, and tooling. Picking matters, but a good API is mostly predictability, idempotency, and clear errors, not the protocol.

Core concepts

Things every API has to handle

  • Resource modeling Name entities and identifiers up front. /users/{id}/orders/{id} is boring and unambiguous — exactly what you want.
  • Versioning URL prefix (/v1/), header, or schema-version field. Pick one and stick to it — v1 lives forever, plan for it.
  • Idempotency keys Any retryable non-GET needs an Idempotency-Key header so retries don't double-charge or double-create.
  • Pagination Cursor-based for anything that grows or changes (feeds, search); offset-based only for static, bounded lists.
  • Errors Structured: an error code (clients switch on it), a human message (for the log), and a request id.
  • Auth Authorization header, never a query param — query strings leak into CDN logs, browser history, and screenshots.

Variants

REST vs GraphQL vs gRPC

REST

request/response

GET /users

GraphQL

single endpoint

{ user(id) }

gRPC

binary RPC

.proto

  • REST HTTP verbs over resources. CDN-cacheable, curl-debuggable, universally supported. Boring is good — the right default for almost any external API.
  • GraphQL One endpoint, the client picks its fields. Great when one backend feeds many frontends (web/iOS/Android/partners). Cost: complex caching, N+1 risk, custom tooling.
  • gRPC Protobuf over HTTP/2 with bidirectional streaming. Fastest on the wire, strict schema, generated clients in every language. Great internal service-to-service; browsers need a gRPC-Web shim.

BFF (backend-for-frontend) is a useful middle ground: a thin REST/GraphQL service per client calling internal gRPC — the client gets exactly what it needs, internal services stay strict.

Trade-offs

What you give up

  • REST is easy to onboard but over-fetches (too much data) or under-fetches (many round-trips for related resources).
  • GraphQL is flexible but the server must defend against expensive queries (depth limits, complexity scoring, persisted queries).
  • gRPC wins on performance but loses the HTTP ecosystem: no easy CDN caching, no browser DevTools tab, no curl one-liners.
  • Versioning is painful regardless — teams promise a clean v2 migration and rarely actually deprecate v1.
  • Idempotency is the difference between a retry and a duplicate charge; skip it and you'll eventually corrupt customer data.

In an interview

What to listen for

  • “Mobile & web, different needs” GraphQL or BFF — don't make mobile do 5 round-trips to render one screen.
  • “Internal microservices” gRPC for hot paths, REST for management and admin endpoints.
  • “Public API for third parties” REST + an OpenAPI spec — stable URLs, documented error codes, generous rate limits.
  • “Webhooks” / “outbound POST” Signed bodies (HMAC), idempotency keys, retries with exponential backoff, a replayable delivery log.

Always say what happens on retry: “POST /payments takes an Idempotency-Key, so a duplicate returns the original response without a second charge.” That sentence beats naming any protocol.