GraphQL Interview Questions and Answers
Schemas, resolvers, queries, mutations and the N+1 problem.
Whether you are preparing for entry-level GraphQL interview questions for freshers or senior software engineer interview questions addressing concurrency, scalability, and system architecture, this track provides peer-reviewed model answers with syntax walkthroughs, edge cases, and practical interview tips.
1 How does GraphQL differ from REST? Easy
GraphQL exposes a single endpoint and a typed schema rather than many resource URLs. The client sends a query describing exactly the fields it needs, so it avoids the over-fetching and under-fetching typical of fixed REST payloads. One request can traverse relationships that would take several REST round trips.
query {
user(id: 42) { name orders(last: 3) { total } }
}
Trade-offs: HTTP caching is harder because everything is usually a POST to /graphql; the server must defend against expensive or malicious queries; and file uploads, long-running jobs and simple CRUD often map more directly to REST. REST also has richer tooling for status codes and content negotiation.
In practice GraphQL suits product UIs with varied data needs, while REST remains strong for public, cacheable, resource-oriented APIs. Many teams use both.
2 What is the difference between a query, a mutation and a subscription? Easy
- Query: a read operation. Queries can run in parallel and must be side-effect free.
- Mutation: a write operation that changes server state. Top-level mutation fields run serially, so
createOrdercompletes beforechargeOrderstarts, while nested selections may still resolve concurrently. - Subscription: a long-lived stream over WebSocket or a similar transport that pushes events to the client when the server publishes them.
mutation {
createOrder(input: { sku: "A1", qty: 2 }) { id }
}
Queries and mutations share the same schema and resolver model. A common mistake is putting writes in a query and losing the serial execution guarantee. Another is assuming mutations give transactional atomicity across fields: they are only sequential, so partial failure must be handled explicitly with error payloads or rollbacks at the service layer.
3 What is a GraphQL schema and what is SDL? Easy
The schema is the contract between client and server: it declares the types, fields, arguments, return types and nullability that all operations are validated against. The Schema Definition Language (SDL) is the human-readable syntax for writing it.
type User {
id: ID!
name: String!
email: String
orders(first: Int = 10): [Order!]!
}
type Query {
user(id: ID!): User
}
A ! marks a non-null field. The schema also defines input types, enums, interfaces, unions and directives. There are two authoring styles: schema-first, where SDL is the source of truth and resolvers are bound to it, and code-first, where the schema is generated from typed code. Introspection exposes the schema for tooling such as GraphiQL and code generators. Because clients build against the schema, treat it as a public contract and evolve it only with backward-compatible additions.
4 What is the N+1 problem in GraphQL and how do you solve it? Medium
Resolvers run per field. If a list resolver returns N items and a child field resolver loads related data one item at a time, you get one query for the list plus N queries for the children.
query { orders { id customer { name } } }
Naively this runs one query for orders and one per order to fetch each customer. The fix is batching within a request: DataLoader collects all keys requested during a tick and issues a single WHERE id IN (...) query, then dispatches the results back to each resolver.
const customerLoader = new DataLoader(ids =>
db.customers.findByIds(ids)
);
DataLoader also caches per request, preventing duplicate loads. Alternatives include join-based root resolvers, persisted query plans, or a query planner that fetches the whole subtree. Always scope the loader to the request, not the process, to avoid stale data across users.
5 What arguments does a GraphQL resolver receive? Medium
A resolver has the signature (parent, args, context, info):
parent(also root/value): the object returned by the parent resolver.args: the arguments supplied to this field, already coerced to schema types.context: a per-request object for shared state such as the authenticated user, database clients and DataLoaders. It is created once per request and is where authorization and tracing are usually wired.info: metadata about the execution, including the field name, return type, path and query AST, useful for logging and projection optimization.
const resolvers = {
Query: {
user: (_, { id }, ctx) => ctx.db.user.findById(id),
},
};
For a default-returning field you can often omit the resolver. Keep resolvers thin: validate and authorize, delegate to services, and avoid embedding business logic that cannot be reused or unit-tested without a GraphQL layer.
6 How should you paginate a GraphQL list field? Medium
Two options:
- Offset/limit:
orders(offset: 40, limit: 20). Easy to implement but unstable when items are inserted or removed and slow at large offsets. - Cursor-based connections following the Relay spec:
orders(first: 20, after: "cursor")returningedges,node,cursorandpageInfowithhasNextPageandendCursor.
type OrderConnection {
edges: [OrderEdge!]!
pageInfo: PageInfo!
}
Cursors encode a stable, indexed sort key so pages do not skip or repeat items under concurrent writes. Always cap first and last to protect the server, make the connection a reusable pattern across fields, and document the sort order. Cursor pagination is more code but is the standard for feeds and anything that changes frequently.
7 How does error handling work in GraphQL? Medium
GraphQL usually returns HTTP 200 with a JSON body containing data and/or errors, because a single operation can partially succeed across fields.
{
"data": { "user": { "name": "Ada", "email": null } },
"errors": [
{ "message": "Email not visible",
"path": ["user", "email"],
"extensions": { "code": "FORBIDDEN" } }
]
}
Each error may include path, locations and extensions. Use extensions.code for stable, machine-readable categories, and avoid leaking internal messages. Null bubbling matters: if a non-null field resolves to null, the null propagates up to the nearest nullable ancestor, which can wipe out large parts of data.
For transport failures such as authentication, returning a 401 is still appropriate. Many teams adopt an explicit result union for mutations so domain errors are typed instead of hidden in the errors array.
8 How do you handle authorization in a GraphQL API? Medium
Authorization belongs in the server layer, never in the client query. Because clients can request any combination of fields, enforce rules at the field and object level, not only at the endpoint.
Common approaches:
- Check permissions in the
contextwhen building it, then re-check per resolver for sensitive fields. - Use schema directives such as
@auth(requires: ADMIN)applied to fields or types, backed by middleware that wraps resolvers. - Return typed errors or null for fields the caller cannot see, and never expose existence of unauthorized data.
type Query {
salary: Int @auth(requires: HR)
}
Beware of leaking data through error messages, counts or relation traversal. Centralize policy in a reusable authorization service, test it per field, and audit new fields because introspection and tooling make the schema map easy to discover. Rate limiting and query cost analysis complement authorization but do not replace it.
9 How do you protect a GraphQL endpoint from abusive queries? Hard
Because clients can compose arbitrarily deep and wide queries, a single request can exhaust the database.
Defences:
- Limit query depth, for example reject anything deeper than ten levels.
- Assign a cost to each field and reject queries whose total exceeds a budget, charging more for list fields and expensive joins.
- Require persisted queries so only pre-approved operations run in production, which also shrinks payloads.
- Disable or restrict introspection in production, and apply per-client rate limits and timeouts.
- Cap pagination arguments and reject requests without a limit.
const server = new ApolloServer({
validationRules: [depthLimit(10), createComplexityLimitRule(1000)],
});
Combine these with database query timeouts and monitoring so a bad query is cut off before it degrades shared resources. Depth alone is insufficient because a shallow query can still request huge lists.
10 What is GraphQL federation and when do you need it? Hard
Federation lets several teams own separate GraphQL subgraphs and compose them into one graph for clients. Each subgraph declares the entities it owns with a key directive, and a gateway or router plans and stitches queries across them.
type User @key(fields: "id") {
id: ID!
name: String!
}
When a query asks for User.name and User.orders, the router fetches the entity from the users subgraph and the orders subgraph and merges the results. Apollo Federation is the best-known implementation; schema stitching is an older, more manual alternative.
You need federation when a single monolithic schema becomes a bottleneck for multiple teams, or when services are owned independently. Costs include router complexity, extra network hops, careful entity key design and harder debugging. For a single team and a small schema, a modular monolith schema is simpler and faster.
Frequently Asked Questions About GraphQL Interviews
What do hiring managers evaluate in GraphQL technical rounds?
Technical interviewers look for foundational fluency, idiomatic syntax, clarity when communicating complex logic, and awareness of performance trade-offs (e.g. memory footprint, render performance, and network latency) in production environments.
What are the best interview tips for practicing GraphQL questions?
Use active recall: summarize each answer in your own words before revealing the model solution. Focus on explaining why a certain approach is chosen rather than just memorizing code syntax.