GraphQL has gained significant adoption at Indian product companies as an alternative to REST for APIs that serve multiple clients (mobile, web, third-party integrations). Understanding GraphQL's schema-driven approach, resolver patterns, and performance optimisation is increasingly expected for senior full-stack and backend roles. This guide covers GraphQL interview questions for India in 2026.
GraphQL fundamentals: schema and operations
GraphQL basics:
1. What is GraphQL? GraphQL is a query language for APIs and a server-side runtime for executing those queries. It was developed at Facebook (2012) and open-sourced in 2015. Key advantages over REST: - Client specifies exactly the data it needs (no over-fetching: no extra fields returned; no under-fetching: no multiple round trips for related data) - Single endpoint: /graphql serves all operations (vs REST where each resource has its own endpoint) - Strongly typed schema: the schema defines all types, queries, and mutations; the contract between client and server is explicit - Introspection: clients can query the schema itself to discover what operations are available
2. GraphQL Schema Definition Language (SDL): ```graphql type User { id: ID! name: String! email: String! orders: [Order!]! }
type Order { id: ID! total: Float! status: OrderStatus! }
enum OrderStatus { PENDING SHIPPED DELIVERED }
type Query { user(id: ID!): User users: [User!]! }
type Mutation { createOrder(userId: ID!, items: [OrderItemInput!]!): Order! }
type Subscription { orderStatusUpdated(orderId: ID!): Order! } ```
3. GraphQL operations: - Query: read data - Mutation: write data (create, update, delete) - Subscription: real-time updates via WebSocket (the server pushes updates to the client when data changes)
4. Arguments, variables, and fragments: Arguments: query user(id: '123') { name, email }. Variables: reusable, type-safe way to parameterise queries (query GetUser($id: ID!) { user(id: $id) { name } }). Fragments: reusable selection sets (fragment UserFields on User { name, email, orders { id, total } }). Aliases: request the same field with different arguments in one query: userA: user(id: '1') { name } userB: user(id: '2') { name }.
Resolvers and the N+1 problem
GraphQL resolvers:
1. Resolvers: Each field in the GraphQL schema is backed by a resolver function. The resolver fetches the data for that field. Resolver signature (Apollo Server): (parent, args, context, info) => data. parent: the resolved value of the parent type. args: the arguments passed to this field. context: shared object (typically contains the database connection, authenticated user, and loaders). info: metadata about the execution (field name, path, etc.).
```ts const resolvers = { Query: { user: async (, { id }, { db }) => db.users.findById(id), users: async (, _, { db }) => db.users.findAll(), }, User: { orders: async (user, , { db }) => db.orders.findByUserId(user.id), }, }; ```
2. The N+1 problem: If you query 10 users and each user's orders field calls db.orders.findByUserId(user.id) separately, you get 1 query for users + 10 queries for orders = 11 queries total. As user count grows, so do queries.
3. DataLoader (the solution): DataLoader batches multiple single-item loads that happen in the same tick into one batch request: ```ts const orderLoader = new DataLoader(async (userIds) => { const orders = await db.orders.findByUserIds(userIds) return userIds.map((id) => orders.filter((o) => o.userId === id)) })
// In resolver: User: { orders: (user, _, { orderLoader }) => orderLoader.load(user.id), } ``` Now 10 user.orders requests are batched into one db.orders.findByUserIds([1, 2, ..., 10]) call. DataLoader also caches results within a request (the same ID loaded twice returns the cached result).
Apollo Server, error handling, and pagination
GraphQL server patterns:
1. Apollo Server: Apollo Server is the most widely used GraphQL server for Node.js. It implements the GraphQL specification, provides a schema-first approach, and integrates with popular frameworks (Express, Fastify, Next.js). Key concepts: typeDefs (the SDL schema as a string or tagged template literal), resolvers (the resolver map), context (function that returns the shared context object for each request).
2. Authentication in GraphQL: Authentication is typically handled in the context function: ```ts const server = new ApolloServer({ typeDefs, resolvers, context: async ({ req }) => { const token = req.headers.authorization?.replace('Bearer ', '') const user = token ? await verifyToken(token) : null return { db, user } }, }) ``` Authorisation: check context.user in resolvers. Schema directives: @auth directive on schema fields that require authentication.
3. Pagination in GraphQL: Offset-based pagination: query users(limit: 10, offset: 20). Simple but has issues with large offsets and inconsistent results if data changes during pagination. Cursor-based pagination (Relay Cursor Connections): the standard for GraphQL pagination. The query returns a connection type with edges (array of { node, cursor }) and pageInfo ({ hasNextPage, endCursor }). The client passes the endCursor as the after argument for the next page. Cursor-based pagination is stable even when data changes.
4. Error handling: GraphQL returns HTTP 200 even for errors (errors appear in the errors array in the response body alongside any partial data). Format: { data: { user: null }, errors: [{ message: 'User not found', path: ['user'], extensions: { code: 'NOT_FOUND' } }] }. Use extensions.code for machine-readable error codes. ApolloError or GraphQLError with extensions allows sending structured errors from resolvers.
Practise GraphQL and API design interview questions with HireStepX's AI voice interviewer. Get scored feedback on your explanations of schema design, resolvers, and the N+1 problem. First 2 sessions free.
Practice freeApollo Federation and GraphQL vs REST trade-offs
GraphQL at scale:
1. Apollo Federation: Apollo Federation builds a distributed GraphQL API (a supergraph) from multiple independently deployable services (subgraphs). Each subgraph: owns a part of the schema, independently deployed, has its own data source. The Apollo Router (or Apollo Gateway): routes incoming queries to the correct subgraphs, stitches the results together, and returns a unified response to the client. Key primitives: @key directive (marks a type's primary key so another subgraph can reference and extend it), @extends (a subgraph extends a type defined in another subgraph).
2. When to use GraphQL vs REST: Use GraphQL when: multiple clients need different subsets of data (mobile, web, third-party partners), complex entity graphs with many relationships, rapid frontend iteration (frontend can add fields without backend changes), you have team structures where frontend teams define their own data needs independently. Use REST when: simple CRUD APIs with fixed data shapes, external public APIs (REST is more familiar to a broad audience), heavy use of HTTP caching (GraphQL POST requests are not cached by default), streaming (REST + SSE or WebSockets is simpler for streaming use cases).
3. GraphQL subscriptions: Subscriptions enable real-time data push from server to client. Implementation: typically over WebSockets (graphql-ws is the modern standard; graphql-subscriptions + PubSub for simple in-process pub/sub). Server publishes events when relevant mutations occur; clients subscribed to those events receive updates. Use for: live order tracking, real-time notifications, collaborative features.
4. GraphQL performance considerations: Query depth limiting: a deeply nested query can cause an expensive database traversal. Limit query depth with graphql-depth-limit. Query complexity analysis: assign a cost to each field; reject queries that exceed the complexity limit (graphql-query-complexity). Persisted queries: clients send a hash of the query; the server looks up the full query; reduces bandwidth and prevents arbitrary query execution. Caching: graphql-response-cache (response-level caching keyed by the query + variables + user context).
Frequently asked questions
Explore more