Top 50 GraphQL Interview Questions & Answers (2026)
About GraphQL
Top 50 GraphQL interview questions covering queries, mutations, subscriptions, schema design, and real-world GraphQL API development. Companies hiring for GraphQL roles test this knowledge at every stage — from first-round screens that check the basics to final-round conversations about real-world trade-offs.
What to Expect in a GraphQL Interview
Expect a mix of conceptual and practical GraphQL questions: clear definitions and core-concept checks for junior roles, hands-on scenario questions for mid-level roles, and architecture or trade-off discussions for senior roles. Interviewers usually move from foundational topics toward the kind of problems you'd actually face on the job.
How to Use This Guide
Work through the GraphQL questions in order — Beginner, then Intermediate, then Advanced — so each concept builds on the last. Every question has its own page; bookmark the ones that trip you up and revisit them the day before your interview.
Curated by Tech Baithak Editorial Team · Last updated: June 2026
Core concepts every GraphQL developer must know.
01
What is GraphQL and who created it?
GraphQL is a query language for APIs and a server-side runtime for executing those queries with your existing data. It was created by Facebook (Meta) in 2012 and open-sourced in 2015. Unlike REST, GraphQL lets clients request exactly the data they need — no more, no less — in a single request. A single GraphQL endpoint replaces dozens of REST endpoints, and the response shape mirrors the query shape. Facebook built it to power their mobile app where over-fetching data on slow networks was a real pain point.
02
What is the difference between GraphQL and REST?
REST exposes multiple endpoints (e.g., /users, /posts), each returning a fixed data shape — leading to over-fetching (getting more data than needed) or under-fetching (requiring multiple requests). GraphQL exposes a single endpoint and lets the client declare exactly what fields it needs in the query. This eliminates over-fetching and solves under-fetching with nested queries. REST uses HTTP verbs (GET, POST, PUT, DELETE) for semantics; GraphQL uses query/mutation/subscription operation types. GraphQL also provides a strongly-typed schema that serves as a contract between client and server.
03
What is a GraphQL query?
A GraphQL query is a read operation used to fetch data from the server. It mirrors the shape of the data you want back. For example: query { user(id: "1") { name email posts { title } } } fetches the user's name, email, and their posts' titles in one round trip. The server returns a JSON object with a data key whose structure exactly matches the query. Queries are sent as HTTP POST requests (or GET with URL-encoded query) to the single GraphQL endpoint.
04
What is a GraphQL mutation?
A mutation is a GraphQL operation for creating, updating, or deleting data — the write equivalent of a query. Example: mutation { createUser(name: "Alice", email: "alice@example.com") { id name } }. Like queries, mutations can return data — you specify which fields you want back after the write operation. Multiple mutations in a single document execute serially (one after another), unlike query fields which can be fetched in parallel. This serial execution prevents race conditions when performing multiple writes.
05
What is a GraphQL subscription?
A subscription is a GraphQL operation for real-time updates. Instead of a one-time response, the server pushes data to the client whenever a specified event occurs. Example: subscription { messageAdded(chatId: "123") { id text author { name } } }. Subscriptions are typically implemented over WebSockets. The client connects, sends the subscription document, and the server streams events. Common use cases include live chat, real-time dashboards, live sports scores, and collaborative editing features.
06
What is the GraphQL Schema Definition Language (SDL)?
The Schema Definition Language (SDL) is the syntax used to define a GraphQL schema — the contract that describes all types, fields, queries, and mutations available in an API. Example: type User { id: ID! name: String! email: String posts: [Post] }. The schema is language-agnostic and human-readable. It defines the root operation types (Query, Mutation, Subscription) and all custom types. The SDL is central to a schema-first approach where the schema is designed before implementation, and it enables powerful tooling like introspection and code generation.
07
What are the scalar types in GraphQL?
GraphQL ships with five built-in scalar types that represent leaf values in the type system. Int is a signed 32-bit integer. Float is a signed double-precision floating-point number. String is a UTF-8 string. Boolean is true or false. ID is a unique identifier — serialized as a string but intended to identify an object (similar to a primary key). You can also define custom scalars (e.g., Date, Email, URL) for domain-specific primitive values that need validation or custom serialization logic.
08
What is an object type in GraphQL?
An object type is the most common type in GraphQL — it represents a named entity with a set of fields. Each field has a name and a return type. Example: type Post { id: ID! title: String! body: String author: User createdAt: String }. Object types can reference other object types (like author: User), enabling deeply nested queries. The two special object types are Query (entry point for reads) and Mutation (entry point for writes). Every field in an object type is ultimately resolved by a resolver function on the server.
09
What is a resolver function in GraphQL?
A resolver is a function on the server that fetches the data for a specific field in the GraphQL schema. Every field in the schema has a corresponding resolver. When a query arrives, GraphQL executes the resolvers for each requested field in a tree-like fashion, starting from the root (Query). A resolver receives four arguments: parent (the result of the parent resolver), args (field arguments from the query), context (shared object for auth, DB connections), and info (query AST info). If no custom resolver is defined, GraphQL uses a default one that reads the same-named property from the parent object.
10
What are variables in GraphQL queries?
Variables allow you to pass dynamic values into a GraphQL query or mutation without embedding them as string literals inside the query document. You declare variables in the operation signature with a $ prefix: query GetUser($id: ID!) { user(id: $id) { name } }. Variables are sent as a separate JSON object alongside the query: { "id": "42" }. This approach is safer (avoids injection attacks), more performant (queries can be cached on the server), and cleaner — the query document stays static and only the variables change between calls.
11
What are arguments in GraphQL?
Arguments are values passed to a field to customize its behavior — similar to function arguments. Any field in a GraphQL schema can accept arguments. Example: { users(limit: 10, offset: 20) { id name } }. Arguments can be used for filtering (status: "active"), pagination (first: 10, after: "cursor"), sorting (orderBy: "createdAt"), or any other field-level customization. Unlike REST query parameters, GraphQL arguments are typed and validated against the schema — passing a wrong type causes a schema validation error before the resolver is even called.
12
What are fragments in GraphQL?
Fragments are reusable units of fields that can be shared across multiple queries. They help avoid repeating the same set of fields. Define a fragment with fragment UserFields on User { id name email avatar } and use it with the spread operator: { user(id: "1") { ...UserFields } }. Fragments must specify the type they apply to (on User) to ensure type safety. They are especially useful in front-end codebases (with tools like Apollo Client) where multiple components need the same fields from a shared type, promoting colocation of data requirements with UI components.
13
What are inline fragments in GraphQL?
Inline fragments are used to conditionally select fields based on the concrete type of a field that is an interface or union. Instead of defining a named fragment, you embed it inline: { search(term: "cat") { ... on User { name email } ... on Post { title body } } }. This is essential when querying a field that can return multiple types (a union or interface). The __typename meta-field is often included alongside inline fragments so the client knows which type was returned and can render the correct component.
14
What are directives in GraphQL?
Directives provide a way to alter the behavior of a query or schema element. GraphQL ships with three built-in directives. @include(if: Boolean) conditionally includes a field if the argument is true. @skip(if: Boolean) conditionally skips a field if the argument is true. @deprecated(reason: String) marks a schema field as deprecated, which documentation tools surface as a warning. Example: { user { name email @include(if: $showEmail) } }. You can also define custom directives for cross-cutting concerns like authorization, caching hints, and input transformation.
15
What is an operation name in GraphQL?
An operation name is an optional but recommended identifier for a GraphQL operation. Example: query FetchUserProfile { user(id: "1") { name } }. While anonymous operations ({ user { name } }) work, named operations are important for production systems. They appear in server logs and analytics, making it easy to trace slow queries or errors. They are required when sending multiple operations in a single document (the client specifies which to execute). Apollo Studio, Datadog APM, and other monitoring tools use operation names to surface performance breakdowns.
16
What are aliases in GraphQL?
Aliases let you rename a field in the response, and more importantly, they allow you to query the same field multiple times with different arguments in a single request. Without aliases, two calls to the same field would conflict in the response JSON. Example: { alice: user(id: "1") { name } bob: user(id: "2") { name } } returns { "alice": { "name": "Alice" }, "bob": { "name": "Bob" } }. Aliases are widely used when comparing data or fetching variations of the same resource in one round trip.
17
What are the three root types in GraphQL?
GraphQL has three root operation types that serve as entry points into the API. Query is the root type for read operations — every field on type Query is a top-level data fetcher. Mutation is the root type for write operations (create, update, delete). Subscription is the root type for real-time event streams. Every GraphQL schema must have at least a Query type. Mutation and Subscription are optional. The schema declaration ties them together: schema { query: Query mutation: Mutation subscription: Subscription }.
18
What is the difference between nullable and non-null types in GraphQL?
In GraphQL, every type is nullable by default — a field can return null if no data is available. Adding a ! suffix makes a type non-null, meaning the server guarantees the field will never be null. Example: name: String! guarantees a string is always returned, while name: String may return null. Non-null is used for required fields like id: ID!. If a non-null field resolver returns null at runtime, GraphQL treats it as an error and bubbles the null up to the nearest nullable parent, potentially nulling out entire subtrees — a behavior to design around carefully.
19
What is the GraphQL Playground and introspection?
GraphQL Playground (and its successor GraphiQL) is an interactive browser-based IDE for exploring and testing GraphQL APIs. It provides auto-complete, syntax highlighting, schema documentation, and query history. It works because of introspection — a built-in GraphQL feature that lets clients query the schema itself. For example: { __schema { types { name } } } returns all types defined in the API. Introspection is powerful for tooling but should be disabled in production to prevent exposing your schema to potential attackers who could use it to craft targeted queries.
20
What is over-fetching and under-fetching in REST and how does GraphQL solve them?
Over-fetching occurs when a REST endpoint returns more data than the client needs — e.g., a /users/{id} endpoint returning 20 fields when the mobile app only displays 3. This wastes bandwidth. Under-fetching occurs when a single endpoint does not return enough data, requiring the client to make additional requests — e.g., fetching a user then separately fetching their posts (N+1 HTTP requests). GraphQL solves both: the client requests exactly the fields it needs (no over-fetching), and nested queries fetch related data in one round trip (no under-fetching). This is especially valuable for mobile clients on slow or metered connections.
Practical knowledge for developers with hands-on experience.
01
What is the N+1 problem in GraphQL and how does DataLoader solve it?
The N+1 problem occurs when fetching a list of N items triggers N additional database queries — one per item. For example, querying 100 posts where each post resolver then queries the database for the author causes 1 (list) + 100 (authors) = 101 queries. DataLoader solves this by batching and caching. It collects all individual load requests within a single event loop tick, then fires one batched query (e.g., SELECT * FROM users WHERE id IN (1,2,...,100)), and distributes results back to the individual resolvers. It is the standard solution in Node.js GraphQL servers and has ports in virtually every backend language.
02
What are persisted queries in GraphQL?
Persisted queries are a performance and security optimization where the client sends only a hash of the query rather than the full query string. The server maps hashes to pre-registered query documents. Benefits include smaller request payloads (especially for large queries), improved CDN and GET-request caching (the full query no longer needs to be in the URL), and enhanced security — you can whitelist only known query hashes, preventing arbitrary queries from being executed (useful in production). Apollo Client supports automatic persisted queries (APQ), which fall back to the full query if the server does not recognize the hash.
03
What is the difference between schema-first and code-first GraphQL approaches?
In the schema-first approach, you write SDL (schema definition language) first, then implement resolvers that match the schema. This makes the schema the source of truth and is great for API-first design where the contract is agreed upon before implementation. Tools like Apollo Server and graphql-tools support this. In the code-first approach, you write resolver code and the schema is generated automatically from it. Libraries like TypeGraphQL (TypeScript decorators) and Strawberry (Python) use this approach. Code-first avoids schema/resolver drift since the schema is derived from the code, but schema-first is often preferred for multi-team APIs where the contract needs to be explicitly designed.
04
What are the pagination strategies in GraphQL?
GraphQL supports two main pagination strategies. Offset-based (page/limit) pagination uses offset: 20, limit: 10 — simple to implement but problematic with live data (insertions cause items to shift, resulting in duplicates or skips). Cursor-based pagination uses an opaque cursor pointing to a specific item: first: 10, after: "cursor123". The Relay Connection spec formalizes this with edges, node, and pageInfo containing hasNextPage, hasPreviousPage, startCursor, and endCursor. Cursor-based is more stable for paginating frequently changing datasets like social media feeds or real-time lists.
05
How is authentication handled in GraphQL?
GraphQL does not prescribe authentication — it is handled at the transport layer (HTTP middleware) and passed to resolvers via the context object. The typical pattern: an HTTP middleware (Express, Fastify) validates the Authorization header (JWT or session token) and attaches the authenticated user to the context. Each resolver then reads context.user to check identity. Example: const resolvers = { Query: { me: (_, __, ctx) => { if (!ctx.user) throw new AuthenticationError('Not logged in'); return ctx.user; } } }. This clean separation means authentication logic lives in one place while resolvers focus on data access.
06
How do you implement field-level authorization in GraphQL?
Field-level authorization restricts which fields a user can access based on their role or permissions. The most straightforward approach is checking permissions inside each resolver: if (!ctx.user.isAdmin) throw new ForbiddenError('Access denied'). A cleaner approach uses the schema directives pattern (e.g., @auth(requires: ADMIN)) or a dedicated library like graphql-shield, which lets you define a permission middleware matrix separate from business logic. GraphQL does not automatically enforce authorization — it is entirely the developer's responsibility to implement it at the resolver level, making a consistent authorization strategy critical.
07
What are custom scalars in GraphQL?
Custom scalars extend GraphQL beyond the five built-in scalar types to handle domain-specific primitive values. Common examples include Date (ISO 8601 string), DateTime, Email, URL, JSON, and UUID. You declare them in the schema: scalar Date, then implement the scalar in code with three methods: serialize (output coercion — how to send to client), parseValue (input coercion — how to parse from variable), and parseLiteral (how to parse from inline query literal). The graphql-scalars library provides production-ready implementations of dozens of common custom scalars.
08
What is the difference between Input types and Object types in GraphQL mutations?
In GraphQL, you cannot use regular object types as mutation arguments — you must use input types. Input types are declared with the input keyword and can only contain scalar types, enums, or other input types (not object types with resolvers). Example: input CreateUserInput { name: String! email: String! } used as createUser(input: CreateUserInput!): User. The reason for this distinction is that object types can have arguments on their fields (resolvers), which does not make sense for inputs. Input types are pure data containers, while object types are resolved data with behavior. Using a single input object argument (instead of many positional args) also makes mutations more forward-compatible.
09
How does error handling work in GraphQL?
GraphQL's error model is unique: the response can contain both data and errors simultaneously. The response is always HTTP 200 with a JSON body containing data and/or errors arrays. Partial success is common — if one resolver fails, GraphQL returns the error for that field but still resolves all other fields. Each error in the errors array has a message, locations, path, and optional extensions for machine-readable codes. For client-facing errors (validation, auth), throw domain-specific errors in resolvers with an extensions.code. Unexpected server errors should be masked with a generic message to avoid leaking implementation details.
10
What is the difference between union types and interface types in GraphQL?
Both unions and interfaces represent a field that can return multiple types, but they differ in structure. An interface defines a set of fields that all implementing types must include: interface Node { id: ID! } — every type implementing Node must have an id field. This enables shared querying of common fields. A union is a set of possible types with no required common fields: union SearchResult = User | Post | Product. Use interfaces when types share fields; use unions when types are completely different but can appear in the same position. Both require inline fragments (... on Type) to query type-specific fields.
11
What is schema stitching versus Apollo Federation in GraphQL?
Schema stitching (from graphql-tools) is an older approach that merges multiple GraphQL schemas into one by combining their types and resolvers at the gateway level. It requires the gateway to know about all sub-schemas and manually define how they connect. Apollo Federation is a newer, more scalable approach where each service owns its own subgraph schema and can extend types owned by other services using @key and @extends directives. The gateway automatically composes the unified supergraph. Federation gives teams true independence — services can be deployed separately, and the gateway generates the query plan automatically. Federation is now the industry standard for microservices-based GraphQL.
12
How do GraphQL subscriptions work over WebSocket?
GraphQL subscriptions use a persistent WebSocket connection between client and server. The client sends a subscription document over the WebSocket, and the server registers a listener on a pub/sub system (e.g., Redis Pub/Sub, in-memory EventEmitter). When the triggering event occurs (e.g., a new message is created), the server publishes to the channel, the subscription resolver is executed for each subscriber, and the resulting data is pushed to the client over their WebSocket. The graphql-ws protocol (or the older subscriptions-transport-ws) standardizes the handshake and message format. Apollo Server and other servers implement this protocol out of the box.
13
What is query depth limiting in GraphQL?
Query depth limiting is a security measure that rejects queries exceeding a maximum nesting depth. Without it, a malicious client can send a deeply nested query like { user { friends { friends { friends { ... } } } } } that causes exponential resolver work and potential server exhaustion (a DoS attack). A depth limit of 5-10 levels is typical. Libraries like graphql-depth-limit implement this as a validation rule. It is part of a defense-in-depth strategy alongside query complexity analysis and rate limiting. The depth is calculated statically on the query document before any resolvers run, making it an efficient early rejection.
14
What is query complexity analysis in GraphQL?
Query complexity analysis assigns a cost score to each field and rejects queries whose total complexity exceeds a threshold. This is more nuanced than depth limiting — a shallow-but-wide query (requesting 100 list items with 50 fields each) can be more expensive than a deep query. You define costs: a scalar field might cost 1, a list field might cost 10, and a database-joining field might cost 100. The total is computed before execution and compared against a configured maximum (e.g., 1000). Libraries like graphql-query-complexity implement this. Exposing the complexity limit and cost of a query in the response helps clients optimize their queries.
15
What is Apollo Client and how does it work with GraphQL?
Apollo Client is a comprehensive state management library for JavaScript applications that handles GraphQL queries, mutations, and subscriptions. It features an intelligent normalized in-memory cache that deduplicates data across queries — fetching a user in one query makes that data available for another query requesting the same user. Apollo Client handles request lifecycle (loading, error, data states), optimistic UI updates (update UI before server confirms), and refetching. It integrates with React via hooks (useQuery, useMutation), Vue, Angular, and more. Its developer tools extension lets you inspect the cache and active queries.
16
What is graphql-tag and DocumentNode?
graphql-tag (imported as gql) is a template literal tag that parses GraphQL query strings into DocumentNode objects — the AST (Abstract Syntax Tree) representation that GraphQL clients like Apollo use internally. Instead of sending raw strings, you write: const GET_USER = gql`{ user(id: $id) { name } }`. The parsing happens once (at import time with module bundlers), not per request. DocumentNode is the TypeScript type from the graphql package representing a parsed GraphQL document. Using gql enables static analysis, syntax highlighting in IDEs with the GraphQL extension, and type generation with GraphQL Code Generator.
17
How do field resolvers work with the parent object in GraphQL?
In GraphQL, each field has a resolver. The parent (also called root or source) argument is the resolved value of the parent type. For example, if a User type has a posts field, the posts resolver receives the resolved User object as its parent: posts: (user, args, ctx) => ctx.db.posts.findByUserId(user.id). For top-level Query fields, the parent is the root value (usually null or an empty object). If no custom resolver is provided, GraphQL's default resolver reads the same-named property from the parent object — so name: (user) => user.name is equivalent to having no resolver at all, enabling concise schema implementations.
18
What are schema extensions in GraphQL?
Schema extensions allow you to add fields or types to an existing schema without modifying the original definition. Use the extend keyword: extend type User { preferredLanguage: String } adds a new field to the User type. This is crucial in modular schema design where different files or modules own different parts of the schema and extend shared types. In Apollo Federation, the extend type Query pattern lets each subgraph add its own fields to the root Query type. Extensions can also add interfaces, directives, and new types to the schema, enabling clean separation of concerns without tight coupling.
19
What is batch loading in GraphQL and how does it improve performance?
Batch loading is the technique of collecting multiple individual data requests and fulfilling them in a single batched operation. In GraphQL, this is primarily implemented via DataLoader. DataLoader uses the JavaScript event loop: within a single tick, it collects all load(key) calls, then on the next tick executes a single batch function with all keys. For databases, this translates to one query with an IN clause instead of N individual queries. Beyond database queries, batch loading applies to external API calls, file reads, and any I/O. DataLoader also implements per-request caching — loading the same key twice in one request returns the cached result without a second database hit.
20
What are GraphQL enums?
Enums (enumeration types) in GraphQL represent a set of allowed string values for a field. They are declared with the enum keyword: enum Role { ADMIN EDITOR VIEWER }. Enum values are validated by the schema — passing an invalid value causes a schema validation error. Enums are internally represented as strings but are type-safe at the schema level. They're useful for status fields (OrderStatus: PENDING, PROCESSING, SHIPPED, DELIVERED), user roles, sort directions (OrderDirection: ASC, DESC), and any other fields with a finite set of valid values. Unlike scalars, enum values are visible in introspection.
Deep expertise questions for senior and lead roles.
01
What is Apollo Federation and how do subgraphs, gateways, and directives like @key and @extends work?
Apollo Federation is an architecture for building a distributed GraphQL API from multiple independent subgraphs. Each subgraph is a standalone GraphQL service that owns a portion of the schema. The Gateway (Apollo Router or @apollo/gateway) composes subgraphs into a unified supergraph and generates a query plan to fetch data from the right services. The @key directive designates the primary key fields of an entity: type User @key(fields: "id") { id: ID! name: String! }. The @extends keyword (Federation v1) or extend type (Federation v2) lets a subgraph reference and extend an entity owned by another service — e.g., the Reviews subgraph extending the User type to add reviews. The gateway uses reference resolvers (__resolveReference) to stitch data across services. Federation enables true team autonomy — each team deploys and iterates on their subgraph independently while the gateway automatically updates the unified API.
02
What is incremental delivery in GraphQL with @defer and @stream?
Incremental delivery is a GraphQL feature that allows the server to send parts of a response progressively as they become ready, rather than waiting for the entire response. The @defer directive marks a fragment that can be delivered later: { user { name ... on User @defer { expensiveComputedField } } }. The client receives an initial response with the non-deferred fields immediately, then receives patches for deferred fragments over a multipart HTTP response. @stream applies to list fields, streaming list items as they are resolved. This dramatically improves perceived performance — users see critical data (like a page title) instantly while slower data loads in the background. Both are part of the GraphQL 2022 specification and require server and client support (Apollo Server 4+ and Apollo Client 3.7+).
03
How do you implement query cost analysis and rate limiting in a production GraphQL API?
Production GraphQL APIs need multiple layers of protection. Query cost analysis assigns weights to fields (scalars: 1, object relations: 5, list fields: multiplied by estimated item count) and rejects queries exceeding a total cost threshold before execution. Libraries like graphql-query-complexity implement this as a validation rule. Rate limiting should be applied per-operation, per-user, and per-IP using token buckets or sliding window algorithms. A sophisticated approach uses cost-based rate limiting: each user has a point budget per time window, and each query deducts its computed cost. Rejected queries return 429 Too Many Requests with a Retry-After header. Additional protections include timeout limits per resolver, maximum query depth, field-level rate limits for expensive resolvers, and disabling introspection in production. All of these should be implemented at the gateway level so they apply uniformly across all subgraphs.
04
What is schema evolution and deprecation strategy in GraphQL?
GraphQL's strongly-typed schema makes evolution both safer and more deliberate than REST. The core principle is additive-only changes: you can add new fields, types, and arguments without breaking existing clients (since clients only request fields they know about). Removing or renaming fields is a breaking change. The deprecation lifecycle uses the @deprecated(reason: "Use newField instead") directive to signal that a field should not be used in new code. Monitoring tools (Apollo Studio) track deprecated field usage by client — only remove a deprecated field after all clients have migrated (usage drops to zero). For argument changes, add new optional arguments rather than modifying existing ones. Schema registries enforce this lifecycle: CI pipelines run schema check commands that fail if a proposed schema change would break any registered client query.
05
What is the Relay specification for GraphQL — Node interface, Connection/Edge pagination?
The Relay spec defines opinionated GraphQL conventions that enable Relay client's powerful caching and pagination features, but have been widely adopted beyond Relay. The Node interface requires every object type to implement interface Node { id: ID! } with a globally unique ID, enabling the root node(id: ID!): Node query to refetch any object by ID. Connection-based pagination wraps list fields: instead of posts: [Post], you have posts(first: Int, after: String): PostConnection where PostConnection has edges: [PostEdge] and pageInfo. Each PostEdge has a node (the actual Post) and a cursor (opaque pagination token). The pageInfo object provides hasNextPage, hasPreviousPage, startCursor, and endCursor. This verbose but powerful structure enables stable cursor-based pagination and per-edge metadata.
06
How do you implement distributed tracing across GraphQL resolvers?
Distributed tracing in GraphQL tracks the execution time and dependencies of every resolver in a query, correlating them with traces across microservices. The key challenge is that a single GraphQL query may fan out to multiple subgraphs, databases, and external services. Apollo Server supports Apollo Tracing (via the tracing extension in the response, now deprecated) and natively integrates with OpenTelemetry — each resolver creates a span with timing data, parent/child relationships, and metadata (field name, return type, arguments). At the Apollo Federation gateway level, the Router creates a root span for the incoming operation and propagates the trace context (traceparent header) to all subgraph requests. Backends like Jaeger, Datadog APM, and Honeycomb visualize the full distributed trace as a waterfall, revealing which resolvers are slowest and whether they run in parallel or serially within the query plan.
07
How does client-side caching work with Apollo Client's normalized cache?
Apollo Client's InMemoryCache normalizes all query results into a flat key-value store where each object is stored by a unique cache key (default: TypeName:id, e.g., User:42). When multiple queries return the same User object, they all reference the same cache entry — updating it in one query automatically updates it everywhere. The cache's read and write methods enable optimistic UI: write the expected result before the mutation completes, then reconcile with the actual server response. Field policies customize how fields are read/written — for example, the keyArgs option controls which arguments are included in the cache key for paginated fields. Cache policies (cache-first, cache-and-network, network-only, no-cache) control staleness behavior. The Apollo Client DevTools extension lets you inspect, search, and modify the cache at runtime.
08
How do you scale GraphQL subscriptions using Redis pub/sub?
A single-server GraphQL subscription implementation (using in-memory EventEmitter) does not scale horizontally — a mutation on Server A cannot notify a subscriber connected to Server B. Redis Pub/Sub solves this by providing a shared message bus. When a mutation occurs, the resolver publishes to a Redis channel: pubsub.publish("MESSAGE_ADDED", { messageAdded: newMessage }). Each server instance subscribes to the Redis channel and pushes events to its connected WebSocket clients. graphql-redis-subscriptions wraps Redis into a GraphQL PubSubEngine. For very high subscriber counts, consider Redis Cluster for horizontal scaling and fan-out patterns where a single publish reaches sharded channels. Apollo Router (Federation gateway) can handle subscription routing, avoiding the need for clients to know which subgraph handles a given subscription. Proper backpressure handling, connection cleanup on client disconnect, and TTL-based channel cleanup are essential for production reliability.
09
What is schema composition and conflict detection in Apollo Federation?
Schema composition in Apollo Federation is the process by which the Apollo Router (or Apollo Studio) merges all subgraph schemas into a single unified supergraph SDL. Composition applies semantic rules to detect conflicts and validate that the merged schema is coherent. Composition errors include: two subgraphs defining the same type with incompatible fields, an @key referencing fields that do not exist, or type extensions for non-existent base types. Composition succeeds only when all rules pass. The schema registry in Apollo Studio stores each subgraph's schema versioned by deployment. The rover subgraph check CLI command runs composition and breaking change detection against all registered client operations in CI, failing the pipeline if the proposed change would break any active client. This creates a safe, automated governance layer that prevents incompatible subgraph deployments from reaching production.
10
What is a schema registry and GraphQL governance at scale?
A schema registry is a central repository that stores versioned GraphQL schemas and enforces governance policies. Apollo Studio is the most widely used registry — it tracks schema changes over time, validates new schemas against registered client operations (preventing breaking changes), and provides usage analytics showing which fields clients query. Governance at scale involves: establishing a schema review process where proposed changes are reviewed by an API design committee before merging; enforcing naming conventions (camelCase fields, PascalCase types, SCREAMING_SNAKE_CASE enum values) via linting tools like graphql-eslint; maintaining a deprecation lifecycle with SLAs for field removal; documenting all types and fields with descriptions in the SDL (they appear in introspection); and running schema checks in CI with tools like Rover CLI. For federation, each team owns their subgraph schema but the gateway team governs the composition rules and the shared entity types, balancing team autonomy with API coherence.