API Paradigms
Resource: Neetcode — System Design for Beginners, Background Video 8
These notes cover the three major paradigms for designing APIs — REST, GraphQL, and gRPC. Each paradigm sits on top of HTTP but makes very different trade-offs around flexibility, performance, and developer experience. Knowing when to reach for each one is a core system design skill.
Contents
0. Preface — What Is an API?
So far we've covered two ways of sending information between a client and a server:
- HTTP Requests — the standard request/response cycle
- WebSockets — persistent, bidirectional connections for real-time data
When clients need to access or modify data inside an application, they do so through an Application Programming Interface (API). An API is a defined contract that specifies what operations are available, what inputs they accept, and what outputs they return.
A familiar local example: the browser's localStorage API exposes localStorage.getItem(key) and localStorage.setItem(key, value). You don't need to know how the browser physically stores the data — the API abstracts that away entirely.
In distributed systems, "API" almost always refers to an external service running on another machine that your code talks to over a network. A database is a classic external service: it exposes an API (SQL queries, or a binary driver protocol) for reading and writing data. A web server is another — it exposes an API for handling HTTP requests from browsers and other services.
There are three dominant paradigms for designing these network APIs:
- REST — Representational State Transfer
- GraphQL — Graph Query Language
- gRPC — Google Remote Procedure Call
1. REST
Representational State Transfer
REST is by far the most widely used API paradigm. It is not a strict protocol — it is an architectural style built on top of HTTP. REST defines a loose set of conventions for how to expose resources over HTTP, rather than a rigid specification. That looseness is both its strength and its weakness.
Statelessness
The most important constraint of REST is that it is stateless: every request must contain all the information needed for the server to process it. The server stores no session state between requests.
Why this matters for scaling: if the server held per-user session state, a load balancer routing the same user to a different server would break that session. With stateless REST, any server can handle any request — no server "owns" a user's state. Anything that needs to persist between requests lives on the client (cookies, localStorage, URL query parameters).
Resources & HTTP Verbs
REST organises everything around resources — the nouns of your system (users, posts, comments, orders). Each resource lives at a URL, and clients interact with it using standard HTTP verbs:
| Verb | Action | Example | Idempotent? |
|---|---|---|---|
| GET | Read a resource | GET /users/42 | ✅ Yes |
| POST | Create a new resource | POST /users | ❌ No |
| PUT | Replace a resource entirely | PUT /users/42 | ✅ Yes |
| PATCH | Partially update a resource | PATCH /users/42 | ✅ Yes |
| DELETE | Delete a resource | DELETE /users/42 | ✅ Yes |
Responses are almost always JSON — a lightweight, human-readable text format. The server also attaches an HTTP status code to signal the outcome of the request:
| Code | Meaning | When It's Returned |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH, or DELETE |
| 201 | Created | Successful POST that created a new resource |
| 400 | Bad Request | Malformed request or invalid input from the client |
| 401 | Unauthorized | Missing or invalid authentication credentials |
| 403 | Forbidden | Authenticated but not permitted to access this resource |
| 404 | Not Found | No resource exists at this URL |
| 500 | Internal Server Error | Unexpected failure on the server side |
REST Request / Response Flow
Trade-offs
Pros:
- Universally understood — every language and framework has HTTP client support
- Stateless design makes horizontal scaling straightforward
- GET responses are HTTP-cacheable by default (browsers, CDNs, and proxies all understand this)
- Human-readable URLs and JSON make debugging easy without special tooling
Cons:
- Over-fetching — the response shape is fixed per endpoint. If a UI only needs a user's name and avatar but the endpoint returns 20 fields, all 20 are sent every time.
- No built-in real-time support — the only way to get live updates over REST is polling, which wastes bandwidth and adds latency
- No enforced schema — client and server can silently drift out of sync without a separate contract (like OpenAPI)
💡REST Is the Right Default
REST should be your starting point for almost any public-facing or internal CRUD API. Its ubiquity means excellent tooling (Swagger/OpenAPI), broad client support, and a mental model that every engineer already knows. Only reach for GraphQL or gRPC when REST's trade-offs are actively hurting you.
2. GraphQL
GraphQL is a query language for APIs developed by Facebook in 2012 and open-sourced in 2015. Rather than exposing a set of fixed endpoints that each return a fixed shape, GraphQL exposes a single endpoint and lets the client declare exactly what data it needs in every request.
Like REST, GraphQL runs over HTTP — but it only uses POST requests, with the query sent in the request body rather than encoded in the URL.
Solving Over-fetching
The core motivation for GraphQL was REST's over-fetching problem. Consider rendering a comment feed where each comment shows only the author's username and avatar:
- With REST:
GET /commentsreturns full user objects for every commenter — email, phone number, bio, account settings — none of which the UI needs. - With GraphQL: the client sends a query specifying
author { username avatar }and only those fields are returned.
Queries, Mutations & Subscriptions
GraphQL operations come in three types:
| Operation | REST Equivalent | Purpose |
|---|---|---|
| Query | GET | Read data — returns only the requested fields |
| Mutation | POST / PUT / PATCH / DELETE | Write data — create, update, or delete resources |
| Subscription | WebSocket / SSE | Push real-time updates to the client when data changes |
Trade-offs
Pros:
- Eliminates over-fetching — clients receive exactly what they ask for, nothing more
- A single request can fetch deeply nested, related data across many resource types (replacing multiple REST round-trips)
- Strongly-typed schema serves as living documentation and enables tooling like query auto-complete and validation
- Adding new fields to the schema is non-breaking for existing clients
Cons:
- Under-fetching can still occur — if a component needs data it didn't originally request, an additional round-trip is required
- HTTP caching is harder — because all requests are POST to a single URL, standard HTTP cache infrastructure (browsers, CDNs) doesn't apply without extra configuration
- Higher server complexity — resolvers, DataLoader batching, and schema design require more upfront engineering
- Overkill for simple services with only a few resource types
⚠️HTTP Caching Doesn't Work Out of the Box with GraphQL
REST GET requests are cacheable by URL — browsers, CDNs, and reverse proxies all handle this automatically. Because GraphQL uses POST for every operation (even reads), standard HTTP caching ignores those requests. Solutions exist (persisted queries, GET-based reads), but they add meaningful complexity. Factor this in before migrating from REST.
📝When GraphQL Shines
GraphQL is most valuable when you have multiple clients with different data requirements — for example, a mobile app that needs a compact summary and a web dashboard that needs rich detail from the same underlying data. Rather than building two REST endpoints or over-fetching on both clients, a single GraphQL schema serves both efficiently.
3. gRPC
gRPC (Google Remote Procedure Call) is a high-performance RPC framework open-sourced by Google in 2015. Where REST and GraphQL think in terms of resources and queries, gRPC thinks in terms of calling a function on a remote machine as if it were a local function call.
Built on HTTP/2
gRPC runs specifically on HTTP/2, which gives it capabilities unavailable to REST running on HTTP/1.1:
| Feature | HTTP/1.1 (REST) | HTTP/2 (gRPC) |
|---|---|---|
| Multiplexing | ❌ One request per connection at a time | ✅ Many concurrent streams per connection |
| Header compression | ❌ Headers re-sent as plain text every request | ✅ Headers compressed with HPACK |
| Streaming | ❌ Response-only (chunked transfer) | ✅ Bidirectional streaming built in |
| Browser support | ✅ Native everywhere | ⚠️ Requires a gRPC-Web proxy layer |
📝gRPC and WebSockets
Because HTTP/2 supports bidirectional streaming natively, gRPC can cover the same real-time use cases as WebSockets — without a separate protocol upgrade handshake. This is one reason gRPC is preferred for server-to-server communication even in latency-sensitive, streaming scenarios.
Protocol Buffers (Protobuf)
Instead of JSON, gRPC uses Protocol Buffers (Protobuf) — a compact binary serialization format defined in a .proto schema file. The schema specifies the structure of every request and response, and the gRPC toolchain auto-generates type-safe client and server code from it in dozens of languages.
// user.proto
syntax = "proto3";
service UserService {
rpc GetUser (GetUserRequest) returns (User);
}
message GetUserRequest {
int32 id = 1;
}
message User {
int32 id = 1;
string name = 2;
string email = 3;
}Once the .proto file is defined, calling the remote service looks identical to calling a local function in the generated client:
# Python — calling a remote gRPC function like a local one
user = stub.GetUser(GetUserRequest(id=42))
print(user.name) # "Alice"gRPC Request / Response Flow
Trade-offs
Pros:
- Significantly faster than REST — Protobuf binary encoding is ~5–10× smaller and faster to parse than JSON
- Auto-generated, type-safe clients eliminate an entire class of integration bugs
- Bidirectional streaming is a first-class feature via HTTP/2
- Strict
.protocontract is enforced at code-generation time — breaking changes are caught before deployment
Cons:
- No native browser support — gRPC requires low-level HTTP/2 control that browsers don't expose. A gRPC-Web proxy is required for any browser client.
- Binary encoding is not human-readable — debugging requires dedicated tooling (
grpcurl, Postman gRPC mode, etc.) - Limited built-in error codes — gRPC's status codes are coarser than HTTP's, and richer error detail requires custom metadata
- Higher setup cost —
.protofiles and code-generation tooling must be integrated into the build pipeline
💡gRPC Is the Default for Internal Microservices
When you control both sides of the connection — as with internal microservices — gRPC is almost always a better choice than REST. You get a stricter contract, better performance, bidirectional streaming, and auto-generated clients. The lack of native browser support is irrelevant for server-to-server communication.
🔑API Paradigm Decisions Are Hard to Reverse
Switching paradigms mid-project is painful. REST → GraphQL requires rewriting resolvers and client queries. REST → gRPC requires introducing Protobuf schemas and regenerating all clients. Like database choices, API paradigm decisions made early are expensive to undo once thousands of clients depend on the existing interface — get it right before launch.
Summary
| Concept | Key Takeaway |
|---|---|
| REST | Stateless, resource-based, JSON over HTTP — the universal default for public APIs |
| Statelessness | Every request is self-contained — enables horizontal scaling without sticky sessions |
| HTTP Verbs | GET / POST / PUT / PATCH / DELETE map to read, create, replace, update, delete |
| HTTP Status Codes | 2xx = success, 4xx = client error, 5xx = server error |
| Over-fetching | REST returns fixed shapes — clients receive fields they don't need |
| GraphQL | Single endpoint, client-specified queries — eliminates over-fetching, harder to cache |
| GraphQL Caching | Uses POST for all requests — HTTP-level caching doesn't apply by default |
| gRPC | Binary Protobuf over HTTP/2 — fastest option, best for internal service-to-service calls |
| Protobuf | Binary schema format — ~5–10× smaller than JSON, auto-generates type-safe clients |
| gRPC Browser Limitation | Cannot be used natively in browsers — requires a gRPC-Web proxy layer |