Zero-Cost Abstractions for @skip and @include in Federated GraphQL - WunderGraph
CEO & Co-Founder at WunderGraph
July 24, 2024·16min read
Last updated on July 1, 2026
We're the builders of Cosmo, a complete open source platform to manage Federated GraphQL APIs. One part of the platform is Cosmo Router, the GraphQL API Gateway that routes requests from clients to Subgraphs, implementing the GraphQL Federation contract.
The Router is a Go application that can be built from source or run as a Docker container. Most of our users prefer to use our published Docker images instead of modifying the source code for customizations.
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.
Compared to a monolithic GraphQL API, distributed GraphQL APIs come with some unique challenges. Instead of just calling resolvers within the same process, the GraphQL Router makes network requests to the Subgraphs, the sub services that implement the resolvers for the federated Schema.
Consequently, the latency of a federated GraphQL Request is determined by the waterfall of network requests from the Router to the Subgraphs. As such, our goal is to keep the number of network requests as low as possible.
This article will look into one specific topic that has a significant impact on the number of network requests: The @skip and @include directives. You might be surprised to learn that some implementations of these directives can lead to additional network requests, which can significantly degrade the performance of your federated GraphQL API.
We're going to look at three different approaches of implementing @skip and @include in a federated GraphQL Router, and we'll discuss the trade-offs of each approach. You'll learn how our implementation evolved over time and how we ended up with a zero-cost abstraction to implement these directives.
What are the @skip and @include directives and why are they so popular?
With the @skip and @include directives, you can conditionally include or exclude fields in your GraphQL response. This is particularly useful when you want to avoid fetching unnecessary data from your backend. Instead of writing multiple queries or fragments for different use cases in your frontend, you can use these directives to conditionally enable or disable parts of your query.
This is especially useful when using Fragments in complex Frontend architectures, as it allows UI components to not just define their data requirements in a colocated Fragment, but also to conditionally include or exclude parts of the Fragment based on the current state of the UI, like for example the user's permissions or the current route.
Instead of using multiple Fragments and combining them at the code level, the @skip and @include directives allow you to handle this logic within the GraphQL Query itself, which has multiple benefits. First, it keeps the UI components more focused and easier to understand, as you don't have to manage the combination of multiple Fragments in your code. Second, this approach allows the GraphQL Server / Router to optimize the query execution, e.g. by combining and joining fields that are requested multiple times across different Fragments.
Query Example
query Query(
$withEmployeeDetails: Boolean!
$employeeId: Int!
$skipMood: Boolean!
) {
...EmployeeDetails @include(if: $withEmployeeDetails)
...EmployeeMood @skip(if: $skipMood)
}
fragment EmployeeDetails on Query {
employee(id: $employeeId) {
# This data is fetched from the Employee Subgraph
details {
forename
surname
}
currentMood @skip(if: $skipMood) # This data is fetched from the Mood Subgraph
}
}
fragment EmployeeMood on Query {
employee(id: $employeeId) {
currentMood # This data is fetched from the Mood Subgraph
}
}
The problem with @skip and @include in a federated GraphQL API
If you take a closer look at the query above, you'll notice that we don't need to fetch the Employee's current Mood from the Mood Subgraph at all if $skipMood is set to true. To be more precise, we want to make zero network requests to the Mood Subgraph if the @skip directive evaluates to true.
You'll see that Query Planners in federated GraphQL Routers can take different approaches to implement the @skip and @include directives. In general, we found three different approaches to solving this problem:
- Subgraph Directive Evaluation Approach: The Router forwards the skip & include directives to all Subgraphs, and the Subgraphs decide whether to include or exclude the field based on the
@skipand@includedirectives. - Smart but expensive Approach: The Router creates an execution plan for all Subgraph Fetches is smart enough to only execute the Subgraph Fetches that are necessary.
- Zero-Cost Abstraction: The Router "Normalizes" the Query in a way that the Query Planner & Subgraphs don't need to know about the
@skipand@includedirectives.
We call the third approach the "Zero-Cost Abstraction" because it has (almost) zero cost in terms of overhead and complexity, but let's first take a look at the first two approaches to understand why they are not ideal, and why we ended up with the Zero-Cost Abstraction.
How a federated GraphQL Router resolves a distributed Query
Employees Subgraph
# Employees Subgraph
type Query {
employee(id: Int!): Employee
}
type Employee @key(fields: "id") {
id: Int!
details: EmployeeDetails
}
type EmployeeDetails {
forename: String
surname: String
}
Mood Subgraph
# Mood Subgraph
type Employee @key(fields: "id") {
id: Int!
currentMood: Mood
}
enum Mood {
HAPPY
SAD
}
Conclusion
In this article, we've looked at three different approaches to implementing the @skip and @include directives in a federated GraphQL Router, and we've discussed the trade-offs of each approach.
We've seen that the Subgraph Directive Evaluation Approach is the simplest way to implement these directives, but it can lead to additional network requests and therefore degrade the performance of your federated GraphQL API.
We've looked at a more sophisticated approach that makes the Router aware of the special meaning of these directives. Although this approach reduces the number of network requests, it makes the Query Planner more complex and expensive to run.
Finally, we've introduced the Zero-Cost Abstraction, which in fact has a negative cost as it reduces the complexity of the Query Planner, Query Execution, as well as the Subgraphs themselves.
If you're keen to leverage the Zero-Cost Abstraction in your federated GraphQL API, check out the Cosmo Stack in the WunderGraph GitHub repository.