GraphQL doesn't solve under & overfetching, Relay & WunderGraph does - WunderGraph
State of GraphQL Federation 2026
How are teams governing schema changes, handling production traffic, and measuring Federation success? Share your experience and get early access to the full report. For every valid survey completed, we'll donate $30 to UNICEF.
When you look at positive articles about GraphQL, you often hear how much better it is than REST and how it solves the problem of overfetching, underfetching and eliminates waterfalls, but is that really true?
Let's explain the terms, look at the problems and evaluate if GraphQL does what we hear in all the praises of the Query language.
You'll learn that GraphQL does not solve the problems of overfetching, underfetching and waterfalls. It's not even close to solving these problems. Read on to understand why this is the case and how we can get the full benefits of GraphQL.
What is overfetching?
Overfetching is the problem of fetching more data than you need. GraphQL advocates usually attribute this problem to REST APIs. REST APIs are often designed to be generic and reusable across different use cases, so it's not uncommon that a REST API returns more data than you actually need.
It's widely known that GraphQL has a native solution to this problem. Through the use of the Selection Set, you can specify exactly which fields you need. So, in theory, it's possible to solve the problem of overfetching with GraphQL. However, we'll see later that this often stays a theory and doesn't work in practice.
Furthermore, the claim that REST APIs cannot solve this problem is also not true. You can use sparse fieldsets to achieve similar functionality in REST APIs.
Here's an example:
GET /api/dragons?fields=id,name,description
The problem with this approach is that it's not standardized and not widely used compared to Selection Sets in GraphQL.
What is underfetching?
In addition to overfetching, GraphQL also claims to solve the problem of underfetching. Underfetching is the problem of not fetching enough data in a single request. This problem is often attributed to REST APIs as well.
In order to load all the data you need to present a page, you have to make a lot of requests, simply because a single request doesn't return all the data you need. This problem comes from the fact that REST APIs are usually too granular, which they have to be in order to be reusable.
When listening to GraphQL advocates, it seems like GraphQL solves this problem as well. You can simply specify all the data you need in a single Query and the GraphQL server will return all the required data in a single response. Again, this works in theory, but does it work in practice?
What is the problem with waterfall requests?
If we look at the problem of underfetching, what actually is the consequence of this problem? How does it manifest itself in a real-world application?
From the user's perspective, it's quite undesirable to have to wait a long time until the page is loaded. This is even worse when the page loads incrementally over a period of 10-15 seconds.
Imagine a website with a lot of nested components, each building on top of each other. In order to render the page, you have to fetch data for each component. This means that you have to wait for the data of the first component to be fetched before you can fetch the data for the second component. This results in a long "waterfall" of requests because we can only render a child component when the data for the parent component is available, which in turn can only be fetched when the data for the grandparent component is available and that component can be rendered.
The waterfall usually looks something like this:
- Render root component
- Fetch data for root component
- Render child components
- Fetch data for child components
- Render grandchild components
- Fetch data for grandchild components
- ...
This is a very simplified example, but it illustrates the problem quite well.
Real-world examples of GraphQL Underfetching, Overfetching & Waterfall requests
To better understand if the claims we hear about GraphQL are true, let's look at a few real-world examples.
If you want to follow along, follow the URLs, open the browser's network tab and have a look at the traffic.
Example 1: Reddit - 42 GraphQL requests to load a subreddit
Reddit is the first example we'll look at. Let's take a look at the GraphQL Subreddit. We can count 42 requests, including preflight requests, so let's just count 21 actual requests.
Reddit uses persisted operations and sends the operation ID in the POST request body. The waterfall is huge.
Example 2: Twitter - 6 GraphQL requests to load the home page
Twitter is quite advanced, actually. If we look at the home page, we can see a few things.
The website makes 6 requests and uses persisted operations. Read requests leverage GET and the operation ID and variables are sent in the query string. Quite good, but still a waterfall of 6 requests.
Example 3: twitch.tv - 16 GraphQL requests to load the home page
Looking at twitch, we can see that their home page makes 16 requests. Twitch uses persisted operations and batching, so the 16 requests actually translate to a total of 32 requests.
Example 4: Glassdoor - 3 GraphQL requests to load the member home page
Next up, we have the Glassdoor member home page. In the network tab, we can see 3 HTTP POST requests, which are GraphQL requests without persisted operations, but they are using batching, so the 3 requests translate to a total of 7 GraphQL Operations.
Example 5: Nerdwallet - 16 GraphQL requests to load the page for best credit cards
Another example, Nerdwallet best credit cards, makes 16 requests to an endpoint they call "supergraph", which might indicate what architecture they use. No persisted operations, no batching, all HTTP POST over HTTP/3.
Example 6: Peleton - 3 GraphQL requests to load the bike page
Peleton's bike page makes 3 requests. They send regular GraphQL requests over HTTP POST without persisted operations or batching. I noticed that they have 2 different GraphQL endpoints.
Example 7: Priceline - 8 GraphQL requests to load a search page
The Search Page of Priceline makes 8 requests. They don't use persisted operations and send GraphQL requests over HTTP POST without batching.
Example 8: Facebook - 10 GraphQL requests to load the home page
Facebook's home page makes 10 requests. I would have expected less, but have a look yourself. They are sending persisted operations over HTTP POST.
Summary of my findings in real-world GraphQL applications
To summarize my findings, I was unable to find a single website that eliminates the problem of underfetching and waterfall requests. Given the amount of GraphQL requests I've seen, it's also quite likely that a lot of the pages suffer from overfetching as well, I'll explain this in more detail later.
GraphQL does not solve the problem of overfetching, underfetching and waterfall requests
Another observation is that most companies use a different style of GraphQL. Some use persisted operations, some use batching, some use both, some use neither. I found different ways of structuring URLs, some use query strings, some use the request body with HTTP POST. There is no real standard, and you might be wondering why.
Why does GraphQL fail to solve underfetching and overfetching in practice?
What's often forgotten is that GraphQL is just a Query language. It doesn't specify how the data is fetched. The GraphQL specification doesn't say much about the transport layer, it's really just a specification for the Query language itself.
So, in theory GraphQL has the capability to solve the problem of underfetching and overfetching, but it takes more than just the Query language to solve these problems. We need a client-server architecture combined with a workflow that encourages us to reduce the number of requests per page.
GraphQL clients need to encourage the use of Fragments
When using GraphQL, there are two ways to specify the data you need. You can either specify the data with a Query at the top of the component tree, or you can use Fragments to specify the data at the component level.
Clients like Relay encourage the use of Fragments, allowing deeply nested components to specify what data they need. These fragments are then hoisted to the top of the component tree and combined into a single query. This pattern is very powerful and comes with other benefits, but most importantly, it allows us to keep components decoupled, while still being able to fetch data efficiently with the least amount of requests.
GraphQL servers need to implement relationships between types over returning IDs
Coming from REST APIs, we are used to returning IDs from GraphQL resolvers. E.g. the user resolver could return IDs for the posts of a user. However, this would not allow us to fetch a tree of data (user -> posts -> comments) with a single request. Instead, we would have to make a request for each post and then for each comment.
To solve this problem, GraphQL servers need to implement relationships between types. So, instead of returning IDs, resolvers should return relationships so that the GraphQL client can fetch deeply nested data with a single request.
GraphQL clients and servers need to support persisted operations
Sending lots of GraphQL Operations over HTTP POST has two problems. First, it's inefficient, because we are sending the same query over and over again. Every client sends the same query, and the server has to parse the same query over and over again. That's not just costly in terms of performance, wasted bandwidth and CPU cycles, but also has security implications.
It's much better to use persisted operations over exposing a public GraphQL endpoint. With persisted operations, you're registering all known Operations on the server during build time. This doesn't just allow you to validate Operations ahead of time, but also optimize them, strip out unused or duplicate fields, and so on.
The topic of this post was about overfetching and underfetching, so you might be wondering why I spend to much time talking about persisted operations. Performance and security concerns aside, persisted operations help us achieve two things.
How Relay & WunderGraph solve the problem of underfetching, overfetching and waterfall requests
I want to emphasize that there's no silver bullet, and there's definitely a learning curve when using Relay & WunderGraph for the first time, but the benefits are huge.
Combining Relay with WunderGraph allows us not just to solve the problem of inefficient data fetching, but we're also making our applications a lot more secure, faster, and easier to maintain. Let's have a look at how the two open source projects can work together.
Through the use of Fragments, we can specify exactly the data we need at the component level. This allows us to keep components decoupled, while still being able to fetch data efficiently with the least amount of requests. Furthermore, colocating the data requirements with the component ensures that we really only fetch the data we need.
Not using this approach ultimately leads to using data that was requested by other components, adding unnecessary coupling between individual components. If it's not clear where the data comes from, developers will be afraid of removing fields from Queries, because they don't know if they are used somewhere else. This will ultimately lead to overfetching. With Fragments and Relay on the other hand, we can be sure that we only use the data we need.
The Relay compiler will hoist all Fragments to the top of the component tree and combine them into a single query for us. Once this process is done, it will generate a hash for each Operation and store it in the wundergraph/operations/relay/persisted.json file. WunderGraph will pick up this file and expand it into GraphQL Operations files, with the hash as the name, and the Operation as the content. Doing so will automatically register the Operation as a JSON RPC Endpoint on the WunderGraph Gateway.
Conclusion
As you can see, Relay and WunderGraph are a perfect match. The Relay compiler generates efficient GraphQL Operations and helps us to keep our components decoupled. Together with WunderGraph, we can make sure to fetch the data as efficiently as possible while keeping our application secure.
As you can see from the examples above, I think there's a lot of potential to "get GraphQL right". Our goal is to provide a standardized way to use GraphQL in your application without all the pitfalls. We believe that Relay is underappreciated and that it needs to be easier to use and more accessible, which is what we're trying to achieve with WunderGraph.
I'm really looking forward to your feedback and ideas on how we can improve the developer experience even further.
If you got interested in trying out the approach, you might want to look at some of these examples