Skip to content
Preptima

Backend engineering fundamentals

What an API contract commits you to, what a JWT genuinely cannot do, where a thread pool goes when a dependency slows down, and the operational details — shutdown, health checks, correlation ids — that only matter once something is on fire. Fifty-six items, twelve worked through with code or a diagram.

56 questions

Go deeper on Backend Engineering
Standard Sheet View

APIs and contracts

What is REST actually asking of you?

Resources identified by URLs, manipulated through a uniform set of methods whose semantics the whole internet already agrees on, with each request carrying everything needed to serve it. The practical value is not elegance: it is that proxies, caches, load balancers and client libraries can reason about your API without knowing anything about it, because GET is safe and PUT is idempotent everywhere. Most APIs called REST are HTTP-with-JSON, which is fine — the honest answer names which constraints you actually kept.

When would you choose RPC over REST?

When the operation is a verb that does not map onto a resource, and the consumer is another service rather than a browser. "Recalculate this invoice" is not a noun, and forcing it into a resource produces contortions like POST /invoices/7/recalculations. RPC frameworks also give you a generated, type-checked client and a binary encoding, which matters on a hot internal path. The cost is that you lose HTTP's generic tooling — caching, standard status semantics, and the ability to debug with a browser.

What does GraphQL solve, and what does it cost?

It solves the client having to make several round trips or accept over-fetching, by letting the caller specify exactly the shape it needs — genuinely valuable when many different clients consume one backend. The costs land on the server: query complexity must be bounded or a single request can ask for something quadratic, resolvers make N+1 the default rather than the exception so batching becomes mandatory, and HTTP caching largely stops working because every request is a POST to one URL with a different body.

Show me the three ways to version an API and what each costs.

Versioning is not a style preference; each option decides who bears the cost of a breaking change.

1. In the path            GET /v2/orders/7
   Obvious, trivially routable, and cache-friendly.
   Cost: every URL changes, so links and bookmarks break, and you often end
   up maintaining two whole codepaths rather than two representations.

2. In a header            GET /orders/7
                          Accept: application/vnd.acme.v2+json
   URLs stay stable and the resource identity is honest — it is the same
   order either way.
   Cost: invisible in a browser, easy to forget, and caches must be told to
   vary on the header or they will serve v1 to a v2 client.

3. No version; never break
   Add fields, never remove or repurpose them. Make new behaviour opt-in.
   Cost: the schema accretes forever and you carry dead fields for years.

The third is the one that scales, and it is what most large public APIs actually do despite having a /v1 in the path that never changed. The discipline behind it is knowing which changes are breaking: removing a field, renaming one, making an optional request field required, narrowing an accepted range, or adding a new value to an enum a client switches on exhaustively.

The one people miss is that last one. Adding an order.status of PARTIALLY_SHIPPED looks additive, and it breaks every client whose switch statement has no default branch. If you expect an enum to grow, say so in the contract from day one and require clients to tolerate unknown values, because you cannot retrofit that.

What makes a pagination contract good?

That it is stable while the data changes underneath it, which rules out offsets for anything users scroll. An opaque cursor — a token the client passes back without interpreting — lets you change the underlying keyset later without a breaking change, and stops clients constructing their own. Return the cursor for the next page and nothing else about position; the moment you return a total page count you have committed to computing it, which on a large table is the expensive part of the query.

Why should a public API avoid exposing database ids?

Because a sequential integer leaks your volume and growth rate to anyone willing to create two records a week apart, and it invites enumeration — walking /users/1, /users/2 and discovering what your authorisation actually checks rather than what you assumed it checked. It also couples the public contract to your storage, so sharding, merging two databases, or migrating to a different engine all change identifiers that customers have hard-coded. A separate opaque public identifier, indexed and generated independently, costs one column and removes all three problems at once. The related discipline is that an unguessable id is not an authorisation control: it raises the cost of enumeration and does nothing about a caller who has legitimately seen the id once.

What is an API gateway doing that a load balancer is not?

Cross-cutting concerns that would otherwise be reimplemented in every service: authentication, rate limiting, request validation, routing by path to different backends, and response shaping for particular clients. A load balancer chooses an instance; a gateway makes policy decisions about the request. The risk is that a gateway accretes business logic, at which point it becomes a shared component every team must change to ship anything — the distributed monolith arriving through the back door. The line worth holding is that a gateway may reject a request, and must not transform it in ways that encode domain rules.

What belongs in an API contract besides the fields?

The error shape, the idempotency semantics of every mutating endpoint, the rate limits and how they are signalled, the pagination scheme, and what "optional" means — absent, or present and null. Those are the parts a client cannot infer and will otherwise discover in production. A schema that documents only the happy path leaves every consumer to invent its own guess about what a 409 means.