Networking · Lesson 7 of 12 · 20 min

HTTP and API Styles: Caching, REST, gRPC and GraphQL

What HTTP methods, status codes and caching headers promise, and when to pick REST, gRPC or GraphQL.

✓ Match HTTP methods and status codes to retry and caching rules✓ Explain conditional requests with ETag and 304✓ Choose between REST, gRPC and GraphQL and name the cost of each
Say it in 30 seconds
  • 304Unchanged: saves the body, not the round trip
  • N+1 → 1Queries after batching GraphQL lookups
  • 429 + Retry-AfterHow to tell clients to slow down
Show what to say

“GET, PUT and DELETE are idempotent so they are safe to retry; POST needs an idempotency key. Caching works through max-age and ETag revalidation, where a 304 saves the bytes. REST is the default for public APIs, gRPC suits internal low-latency or streaming calls, and GraphQL suits many clients needing different fields, if I handle N+1 and cost limits.”

Trade-offs

  • REST vs gRPC: REST is universal and cacheable by URL; gRPC is smaller, typed and streams but needs a proxy for browsers.
  • REST vs GraphQL: fixed endpoints are simple to cache and secure; GraphQL fits flexible clients but adds N+1, cost limits and weak HTTP caching.
  • Offset vs cursor pagination: offset is simple but skips and repeats rows as data changes; a cursor is stable and fast.

Bring it up before they ask

Retrying a timed-out POST can charge twice, so mention the idempotency key before the interviewer asks about failure.

0 of 0 sections ticked
30-second answer

HTTP gives you methods with promises (safe, ), status codes, and through Cache-Control and ETags. REST is resources over HTTP, is typed binary calls over , and lets the client choose the fields; each fixes one problem and brings its own.

HTTP semantics that interviews probe

Methods carry promises that clients, and retries rely on.

Read more · 170 words

Safe means it does not change anything; means doing it twice leaves the same state as doing it once.

MethodSafeIdempotent
GETyesyes
PUT, DELETEnoyes
POSTnono

PATCH is not guaranteed idempotent. Status classes: 2xx worked, 3xx look elsewhere, 4xx the client erred (401 not logged in, 403 not allowed, 404 missing, 409 conflict, 429 slow down), 5xx the server erred (502 and 504: a gateway got a bad or no answer upstream).

Caching and conditional requests

Cache-Control: max-age=60 lets the browser reuse a response for 60 seconds with no request.

Read more · 42 words

After that it revalidates: it sends the ETag it saved as If-None-Match, and an unchanged resource answers 304 with no body.

A 304 saves the bytes, not the round trip. Use max-age for things that change rarely, and no-store for private data.

Overview

Within max-age the browser answers from its cache with no request. After that it asks with If-None-Match: an unchanged resource returns 304 with no body, a changed one returns 200 with the new body.

HTTP caching: fresh, revalidated, or changedWithin max-age the browser answers from its cache with no request. After that it asks with If-None-Match: an unchanged resource returns 304 with no body, a changed one returns 200 with the new body.alt [Fresh]Inside max-age: answered from the browser cache. No request is sent.
  • Client
  • Service
Text description
    import { createHash } from 'node:crypto';
    
    // An ETag is a fingerprint of the response body. The browser sends it back in If-None-Match,
    // and if nothing changed the server answers 304 with no body: the bytes are saved, not the round trip.
    export const etagOf = (body) => `"${createHash('sha1').update(body).digest('hex').slice(0, 16)}"`;
    
    export function respond(body, requestHeaders = {}) {
      const etag = etagOf(body);
      const headers = { etag, 'cache-control': 'max-age=60' }; // fresh for 60 s: no request at all
      if (requestHeaders['if-none-match'] === etag) return { status: 304, headers };
      return { status: 200, headers, body };
    }

    REST: resources and verbs

    REST models the API as resources (nouns with URLs) acted on by HTTP verbs: GET /orders/42, POST /orders.

    Read more · 87 words

    Requests are : each carries everything the server needs, so any instance can answer. That is what makes REST easy to , load balance and debug.

    Its weaknesses are chattiness (several round trips for one screen) and fixed response shapes. More in the HLD path.

    gRPC: typed contracts over HTTP/2

    defines services in a .proto file, generates client and server code, and sends compact binary protobuf over .

    Read more · 67 words

    It supports unary calls and server, client and bidirectional streaming, and every call carries a deadline that propagates through the chain of services.

    It shines between internal services. The costs: browsers need a proxy (gRPC-Web), payloads are not human-readable, and URL-based is lost.

    GraphQL: ask for exactly what you need

    exposes one endpoint and a typed .

    Read more · 112 words

    The client sends a query naming the fields it wants, so a phone screen and a dashboard each get their own shape from one API, with one round trip.

    The classic trap is N+1: a list of 100 posts, and each post's author field runs its own query. A loader batches every lookup in the same tick into one call.

    Also plan for query cost and depth limits, per-field authorization, and (everything is a POST to one URL, so cache by object ID in the client instead).

    // The N+1 problem: a list of 100 posts, and each post's author is fetched with its own query.
    // A loader collects every load() made in the same tick and sends ONE batched call instead.
    // Results are also cached per request, so the same key is fetched only once.
    export function makeLoader(batchFn) {
      let queue = [];
      const cache = new Map();
    
      return function load(key) {
        if (cache.has(key)) return cache.get(key);
        const promise = new Promise((resolve, reject) => {
          queue.push({ key, resolve, reject });
          if (queue.length === 1) {
            queueMicrotask(async () => {
              const batch = queue; // everything asked for in this tick
              queue = [];
              try {
                const values = await batchFn(batch.map((b) => b.key));
                batch.forEach((b, i) => b.resolve(values[i]));
              } catch (err) {
                batch.forEach((b) => b.reject(err));
              }
            });
          }
        });
        cache.set(key, promise);
        return promise;
      };
    }

    Choosing between them

    REST, gRPC or GraphQL?yesyesyesnononoREST over JSONgRPCGraphQLREST over JSONMany client types thateach need differentfields?Service to service, withtight latency orstreaming?Is it a public API, orcalled from browsers andthird parties?

    Is it a public API, or called from browsers and third parties?

    Idempotency and retries

    Networks fail after the server has acted but before the client hears.

    Read more · 143 words

    The client cannot tell, so it retries. For GET, PUT and DELETE that is safe. For POST /payments it can charge twice.

    The fix is an : the client sends a unique Idempotency-Key header, and the server stores the result under that key and returns it for any repeat. Pair retries with and , and only retry on errors that are plausibly transient (timeouts, 502, 503, 504, 429 with Retry-After).

    Errors, versioning and pagination

    Read more · 164 words
    • Errors. Return a consistent body (a code, a message, a request ID) with the right status. Never return 200 with an error inside.
    • Versioning. Prefer additive changes. When you must break, version in the URL (/v2/orders) or a header, and keep the old one alive until clients move.
    • Pagination. Offset pagination skips and duplicates rows when data changes and gets slow on deep pages. Cursor pagination ("after this id") is stable and fast.
    • . Answer 429 with Retry-After so well-behaved clients back off.

    Follow-up questions

    Which HTTP methods are idempotent, and why does it matter?

    GET, PUT, DELETE (and HEAD, OPTIONS) are . It matters because clients and proxies may retry them after a timeout. POST is not, so it needs an .

    How does HTTP caching work?

    Cache-Control says how long a response is fresh. When stale, the client revalidates with If-None-Match and the server returns 304 if the ETag still matches.

    REST or gRPC for internal services?

    for latency-sensitive, high-volume or streaming calls where both ends are your code. REST where humans, browsers or partners call it, or where HTTP matters.

    What is the N+1 problem in GraphQL and how do you fix it?

    A list resolver triggers one query per item for a nested field. Batch the lookups with a loader so the whole list costs one query.

    Check yourself

    Question 1 of 7

    Score 2 out of 3 or better to mark this page complete.

    1. A client times out on PUT /users/7 with a full new profile and retries. Is that safe?

    Next: Requests need identity. Next: cookies, sessions, JWT and CORS, then load balancers and proxies.