[Week 3]: API Design for System Design Interviews: REST, GraphQL, gRPC, and How to Choose
REST, GraphQL, and gRPC Explained From First Principles, With the Trade-offs Behind Each and a Step-by-Step Framework for Choosing the Right One in Any System Design Interview
What This Blog Will Cover
Why API design matters in interviews
REST explained in depth
GraphQL explained in depth
gRPC explained in depth
A clear decision framework
This is the third post in the eight-week From Foundations to FAANG-Ready series. If you missed earlier weeks, Week 1 covered scalability, latency, and load balancing. Week 2 covered distributed systems fundamentals including consistency, replication, sharding, and queues. Here is the full series:
Week 1: System Design Fundamentals: Scalability, Latency, and Load Balancing (published)
Week 2: Distributed Systems Fundamentals: Consistency, Replication, Sharding, and Queues (published)
Week 3: API Design for System Design Interviews: REST, GraphQL, gRPC, and How to Choose (you are here)
Week 4: Data Modeling and Database Design: SQL vs. NoSQL and How to Choose the Right One
Week 5: How to Draw a High-Level System Architecture That Impresses Interviewers
Week 6: Detailed System Design: How to Go Deep, Find Bottlenecks, and Discuss Trade-offs
Week 7: System Design Case Studies: Design a URL Shortener and Design a Twitter Feed
Week 8: System Design Case Studies: Design a Chat System and Design a Rate Limiter
Subscribe to get all eight weeks delivered to your inbox as they publish.
The Hook: The API Question That Trips Most Candidates
There is a moment in almost every system design interview where the interviewer asks how clients will talk to the system.
The candidate says REST and moves on. The interviewer asks why not GraphQL or gRPC.
The candidate says REST is simpler or REST is standard without being able to say precisely what that means or what they would give up by using it.
This exchange reveals something specific.
The candidate knows that REST exists and knows it is a reasonable default. What they do not know is what REST actually is at an architectural level, how it differs from GraphQL and gRPC at the mechanism level, and what the trade-offs of each choice are against the specific requirements of the system being designed.
This matters because API design is not a detail.
The API is the contract that defines how every client interacts with the system.
The wrong API style forces clients to make multiple round trips where one would do, or requires the server to send far more data than the client needs, or makes mobile clients slow because they cannot filter responses, or creates tight coupling between services that makes the system hard to evolve.
These are real consequences that flow from the API choice, and interviewers ask about it precisely because the reasoning reveals how a candidate thinks about system boundaries.
This week covers REST, GraphQL, and gRPC in depth, what each one is at an architectural level rather than just what it is called, the problems each one solves and the problems it introduces, and a decision framework for choosing between them based on specific requirements rather than habit.
Why API Design Is Foundational
Before the three approaches, it is worth establishing why API design belongs in a system design interview at all.
An API is a contract between two pieces of software. It defines what requests can be made, what format they take, what the responses look like, and what errors are possible.
Everything about the system that the contract does not specify is the system’s private business. This abstraction boundary is what allows the system to change its internal implementation without breaking every client that depends on it.
The API choice shapes several things that matter enormously at scale.
It shapes payload efficiency.
How much data travels over the network for each client interaction determines both latency and bandwidth cost.
An API that always sends full resource representations to mobile clients on slow networks is more expensive and slower than one that lets clients request only the fields they need.
It shapes round trip count. An API that requires five requests to assemble the data for one screen is five times more expensive in latency than one that assembles it in one. On high-latency mobile connections, the difference between one and five round trips is the difference between a usable experience and an unusable one.
It shapes type safety and code generation.
An API with a strict schema allows client code to be generated automatically, eliminating a class of integration bugs. An API without a schema requires manual client implementation that can fall out of sync with the server.
It shapes evolvability.
An API that breaks clients when fields are added or removed requires coordinated deployments across every service and client. An API with careful versioning or a flexible query model allows evolution without coordination.
Each of the three approaches makes different choices on each of these dimensions. Understanding those choices is what makes the API decision defensible.
REST: The Architectural Style That Dominates the Web
REST is not a protocol, a specification, or a library. It is an architectural style, a set of constraints that, when applied to a distributed system, produce certain desirable properties. Understanding this is the first thing that separates a shallow REST answer from a deep one.
The Six Constraints of REST
Roy Fielding defined REST in his 2000 dissertation through six architectural constraints. Most engineers know one or two of them. Knowing all six and understanding what they produce is what makes REST reasoning precise.
Client-server separation means the client and server are independent. The client handles the user interface. The server handles data storage and business logic. They communicate through the API and evolve independently.
Statelessness means each request from a client to a server must contain all the information the server needs to handle it. The server stores no session state between requests. Every request is complete in itself. This is the constraint that makes REST services easy to scale horizontally: because any server can handle any request, a load balancer can route freely across a fleet of identical servers.
Cacheability means responses must label themselves as cacheable or non-cacheable. Cacheable responses can be stored by clients and intermediaries and reused for subsequent identical requests, reducing latency and server load. HTTP already provides a rich caching mechanism through Cache-Control, ETag, and Last-Modified headers.
Uniform interface is the constraint that gives REST its distinctive character. It requires resources (the things the API deals with) to be identified by stable identifiers (URLs), manipulated through representations (JSON or XML bodies), described self-descriptively (including content type and enough information for the client to process the response), and linked through hypermedia (responses contain links to related resources). In practice, the uniform interface is what most people mean when they say REST: consistent resource-oriented URLs, standard HTTP methods, and predictable response structures.
Layered system means a client cannot tell whether it is talking directly to the server or through an intermediary like a load balancer, proxy, or CDN. Each layer sees only the adjacent layer. This enables infrastructure changes without affecting clients.
Code on demand (optional) means servers can extend client functionality by sending executable code. JavaScript served by a web server is the canonical example. This constraint is rarely discussed in API design contexts.
REST in Practice
In practice, REST APIs organize their interfaces around resources, named as nouns, and use standard HTTP methods to perform operations on them.
GET reads a resource.
POST creates one.
PUT replaces one.
PATCH updates part of one.
DELETE removes one.
A REST API for a social platform might expose resources at URLs like users, posts, and comments, with nested resources for relationships like the posts belonging to a user. Each resource is a stable identifier that the client stores and uses directly.
REST benefits enormously from HTTP infrastructure. Caching, authentication via Authorization headers, content negotiation via Accept and Content-Type headers, and status codes that communicate the outcome of each operation are all built into HTTP and available for free to any REST API. This is a large part of why REST became the dominant style for web APIs: it builds on infrastructure that the entire web already supports.
What REST Does Not Do Well
REST has real weaknesses that motivate the alternatives.
Over-fetching is when the server returns more data than the client needs. A mobile client that needs only a user’s name and avatar to display a comment gets the user’s full profile including fields it will never use. The extra data wastes bandwidth and increases parsing time on a low-powered device.
Under-fetching is the opposite: the server’s resource model does not match the client’s data needs, forcing multiple requests to assemble what one should provide. To display a social media post with the author’s name and the first three comments, a client might need to request the post, then the user, then the comments, across three separate round trips.
Versioning is genuinely hard in REST because the uniform interface constraint means URLs should be stable resource identifiers, but adding v2 to every URL violates the spirit of that constraint. Different approaches (URL versioning, header versioning, media type versioning) each have trade-offs and none is universally satisfying.
Discoverability of API capabilities requires documentation. Unlike protocols with explicit schemas, a REST API’s contract is typically expressed in human-readable documentation that can fall out of sync with implementation.
GraphQL: A Query Language for APIs
GraphQL is not a protocol or an architectural style. It is a query language for APIs and a runtime for executing those queries. This distinction matters because it explains how GraphQL differs from REST at a fundamental level.
The Core Idea
In REST, the server defines the resources and the client asks for them. The client gets what the server decided to include.
In GraphQL, the client defines exactly what data it wants in a structured query, and the server returns precisely that, nothing more and nothing less. The shape of the response matches the shape of the query.
A GraphQL query for a user’s name and their last three posts specifies exactly those fields.
The server returns a JSON response with exactly those fields. No extra fields. No additional requests. The client is in control of the response shape.
This solves both over-fetching and under-fetching simultaneously. The client gets exactly what it asks for in one request, regardless of how that data is spread across the underlying data model.
The Schema
Every GraphQL API is defined by a schema that describes the types of data available and the relationships between them.
The schema is a contract expressed in the GraphQL schema definition language, a formal, machine-readable specification of what can be queried.
The schema serves two purposes. It tells clients exactly what is available and what the shape of the data is, enabling tooling that provides autocomplete, validation, and code generation for client queries. And it gives the server a clear specification of what it must implement, making the contract explicit in a way that REST documentation cannot match.
Queries, Mutations, and Subscriptions
GraphQL organizes operations into three types.
Queries read data.
Mutations change data.
Subscriptions establish a persistent connection for real-time updates, typically over WebSockets.
A mutation in GraphQL is structurally similar to a query: it specifies the operation, the inputs, and exactly which fields of the result the client wants returned. This consistency of structure across read and write operations is one of GraphQL’s ergonomic strengths.
Where GraphQL Wins
GraphQL is the right choice when clients have diverse and specific data needs that differ from each other.
A mobile app that needs minimal data for performance and a web app that needs rich data for a full-featured experience can both use the same GraphQL API, each requesting exactly what it needs. Building separate REST endpoints for each client’s data shape would require constant backend changes as the clients evolve. GraphQL pushes that flexibility to the client.
GraphQL is also well suited when the product is evolving rapidly and data requirements change frequently. Because clients specify their own queries, adding a new field to a type does not break any client (they simply do not query it). Removing a field requires deprecation rather than versioning. The schema evolution story in GraphQL is cleaner than REST versioning.
Where GraphQL Struggles
GraphQL is harder to cache than REST. HTTP caching works on URLs: the same URL always returns the same response, so CDNs and browsers can cache it aggressively. GraphQL sends all requests to a single endpoint using POST, which HTTP infrastructure does not cache by default. Caching requires application-level solutions that add complexity.
GraphQL also puts more responsibility on the server. Because clients can query arbitrarily nested and complex graphs of data, a poorly designed query can trigger enormous amounts of database work.
A query for all users, each with their posts, each with their comments, each with their authors, might seem reasonable in the schema but generate an extremely expensive set of database queries. This is the N+1 problem, and addressing it requires tools like DataLoader that batch and cache data fetching.
GraphQL is less appropriate for simple APIs or public APIs where client diversity is low and caching is important. It adds schema complexity that small teams or stable products may not benefit from.
gRPC: High-Performance Service Communication
gRPC is a remote procedure call framework built by Google on top of HTTP/2 and Protocol Buffers. Where REST and GraphQL focus on resource or query-oriented APIs, gRPC focuses on calling methods on remote services as if they were local function calls.
The Core Idea
In gRPC, the API is defined as a set of methods with typed inputs and outputs. The client calls a method and gets a response. The mechanism of how the call travels over the network is handled by the gRPC framework and is invisible to the application code.
This is different from REST and GraphQL in a fundamental way.
REST is resource-oriented: the client interacts with resources.
GraphQL is query-oriented: the client specifies data requirements. gRPC is procedure-oriented: the client calls methods.
Protocol Buffers
gRPC uses Protocol Buffers (protobuf) as its interface definition language and serialization format. A protobuf definition file specifies the service, its methods, and the message types of the inputs and outputs. From this definition file, gRPC generates client and server code in virtually any language.
The generated code is strongly typed, meaning the compiler catches type mismatches between the client and server at build time rather than at runtime. This eliminates an entire category of integration bugs that REST APIs are susceptible to when JSON fields have unexpected types.
Protobuf serialization is binary rather than text-based, making it significantly more compact than JSON. A protobuf message might be five to ten times smaller than the equivalent JSON, reducing both bandwidth usage and parsing time.
Streaming
gRPC on HTTP/2 supports four communication patterns: unary (one request, one response), server streaming (one request, stream of responses), client streaming (stream of requests, one response), and bidirectional streaming (stream of requests, stream of responses).
These patterns enable use cases like real-time data feeds and large file uploads that REST handles awkwardly.
Where gRPC Wins
gRPC is the right choice for internal service-to-service communication where performance matters and both sides of the connection are controlled. The binary serialization, HTTP/2 multiplexing, strong typing, and code generation make gRPC faster and safer for service meshes and microservice architectures than REST.
gRPC is particularly valuable when services are implemented in multiple languages. The protobuf definition generates correct, idiomatic client and server code in every supported language, ensuring consistency across the polyglot service landscape of a large engineering organization.
Where gRPC Struggles
gRPC is not natively supported by browsers. Making gRPC calls from a web browser requires a proxy layer (gRPC-Web) that translates between the browser’s HTTP/1.1 capabilities and gRPC’s HTTP/2 requirements. This is an additional component that adds latency and operational complexity.
gRPC is also harder to debug than REST. Binary protobuf payloads are not human-readable without deserialization tools, unlike JSON which any developer can read in a browser’s network tab. The tooling ecosystem around gRPC is growing but is less mature and widespread than the REST tooling ecosystem.
Public APIs that must be accessible to arbitrary third-party clients with minimal friction are generally better served by REST, which every HTTP client and every programming language already supports without any additional setup.
The Decision Framework
With all three approaches understood, the decision becomes a matter of matching the right tool to the specific requirements. Here is the framework that produces a defensible choice.
Step 1: Who Is the Client?
If the client is a web browser making calls from the frontend of a web application, REST or GraphQL are the natural choices. gRPC’s lack of native browser support makes it awkward unless a gRPC-Web proxy is acceptable overhead.
If the client is a mobile application with diverse data needs and bandwidth constraints, GraphQL’s ability to request exactly the needed data is a significant advantage.
If the client is another service within the same system, gRPC’s performance, type safety, and code generation make it the strong default for internal communication.
If the client is a third-party developer building an integration, REST’s universality makes it the lowest-friction choice. Every language and every HTTP client already supports it.
Step 2: What Is the Data Shape Problem?
If clients have diverse and specific data requirements that differ from each other, GraphQL’s query flexibility addresses the over-fetching and under-fetching problems directly.
If data requirements are consistent and well-matched to stable resource representations, REST’s resource model is clean and sufficient.
If the API is about invoking actions on remote services rather than querying resources, gRPC’s method-oriented model fits naturally.
Step 3: How Important Is Performance?
If raw throughput and low latency between services are critical, gRPC’s binary serialization and HTTP/2 multiplexing provide a measurable advantage.
If caching is important for reducing server load and client latency, REST’s URL-based cacheability is a significant advantage that GraphQL lacks without additional complexity.
If the performance concern is round trips on high-latency mobile connections, GraphQL’s ability to aggregate data in one request is the performance advantage that matters.
Step 4: How Important Is Type Safety?
If type safety across service boundaries is a priority, gRPC’s protobuf-based code generation catches mismatches at compile time. GraphQL also provides a schema that enables client-side type generation, though the generated types are not as complete as gRPC’s. REST provides no native type safety.
Putting It Together
A complete API decision in a system design interview sounds like this: “For the client-facing API I’d use REST.
The clients are third-party developers and browser-based applications, and REST’s universality means they can integrate without any special setup.
The resource model maps cleanly to the entities in this system and the data requirements are consistent enough that over-fetching is not a significant concern.
For the internal communication between the notification service and the user service, I’d use gRPC. Both services are in the same fleet, performance on that call path matters, and the generated client code from the protobuf definition ensures type safety across the service boundary without any manual client implementation.”
This answer names both choices, justifies each against the specific requirements, and acknowledges the trade-offs implicitly by matching the tool to the context.
API Design Principles That Apply to All Three
Regardless of which style is chosen, several design principles improve the quality of the API.
Design for the consumer. The shape of the API should reflect how clients will use it, not how the server is internally organized. An API that mirrors the database schema or the internal service structure forces clients to understand the server’s implementation, defeating the abstraction the API is supposed to provide.
Be consistent. Every endpoint should follow the same conventions for naming, error response format, and pagination. Inconsistency forces clients to handle each endpoint differently, which adds complexity and causes bugs.
Make errors informative. An error response should tell the client what went wrong, why, and when possible how to fix it. A generic error message without identifying which field was invalid or what constraint was violated is unhelpful and forces clients to guess.
Version carefully. Adding fields is backward compatible. Removing or renaming fields breaks clients. A deprecation cycle with advance notice is the right way to evolve a breaking change. In REST this typically means URL versioning. In GraphQL it means field deprecation with a migration path. In gRPC it means careful field numbering in protobuf definitions.
Paginate collections. Never return an unbounded collection. Cursor-based pagination performs better than offset pagination on large datasets and is consistent when data changes between pages.
Key Takeaways
REST is an architectural style built on six constraints, not just a convention for using HTTP methods. Its strengths are universality, HTTP infrastructure compatibility, and URL-based cacheability. Its weaknesses are over-fetching, under-fetching, and versioning complexity.
GraphQL is a query language that lets clients specify exactly the data they need in a single request. Its strengths are eliminating over-fetching and under-fetching and enabling schema-driven development. Its weaknesses are the loss of HTTP caching, the N+1 query problem, and added schema complexity.
gRPC is a remote procedure call framework built on HTTP/2 and Protocol Buffers. Its strengths are binary serialization performance, strong typing, code generation, and streaming. Its weaknesses are no native browser support and limited tooling compared to REST.
The decision framework starts with who the client is (browser, mobile, service, or third-party), then the data shape problem (diverse needs, consistent needs, or action-oriented), then performance requirements, and then type safety needs.
Most systems use more than one style: REST for external client-facing APIs because of universality, GraphQL for complex product APIs with diverse client data needs, and gRPC for internal service-to-service communication where performance and type safety matter.
API design principles apply across all three: design for the consumer, be consistent, make errors informative, version carefully, and always paginate collections.
Next week in Week 4, we cover data modeling and database design: how to model entities and their relationships, how to choose between SQL and NoSQL, and the database decisions that interviewers probe most heavily.
If you have not already subscribed, do so now so Week 4 lands in your inbox when it publishes.












Shouldn't we include query method also in the list of HTTPS method.
Hi, which tool is used to draw the diagrams in the this material?