InterviewHack.ai
Empezar gratis
Blog/GraphQL Interview Questions and How to Answer Them (35+ Questions)

GraphQL Interview Questions and How to Answer Them (35+ Questions)

September 16, 2026

graphqlapi

A comprehensive GraphQL interview preparation guide covering 45 numbered questions with detailed answers and real code examples. Spans beginner through advanced topics including schema design, performance optimization, federation, security, and production best practices.

GraphQL Interview Questions and How to Answer Them (35+ Questions)

GraphQL interviews separate candidates who have read the docs from those who have built real things with it. This guide covers every question you are likely to face — from "what is GraphQL" to federation, dataloader optimizations, and security hardening — with honest, complete answers and working code.


How to Use This Guide

Work through sections in order if you are new. Jump to the advanced sections if you already ship GraphQL daily. Every answer is written the way a strong senior engineer would say it out loud — not a textbook definition, but the kind of answer that makes an interviewer nod and move on.


Section 1: Fundamentals (Questions 1–10)


1. What is GraphQL and how does it differ from REST?

GraphQL is a query language for APIs and a runtime for executing those queries against your data. Facebook open-sourced it in 2015 after using it internally since 2012.

The core difference from REST is who decides the shape of the response. In REST, the server decides: you hit /users/42 and you get whatever fields the server chose to include. In GraphQL, the client decides: you ask for exactly the fields you need and you get exactly those fields back.

Three structural differences matter most in interviews:

| Dimension | REST | GraphQL |

|---|---|---|

| Endpoint count | Many (/users, /posts, /comments) | Usually one (/graphql) |

| Over/under-fetching | Common | Eliminated by design |

| Versioning | URL versioning (/v2/users) | Schema evolution with @deprecated |

| Type system | Optional (OpenAPI) | Built-in, mandatory |

What interviewers want to hear: You understand the trade-offs, not just the pitch. REST is simpler to cache at the HTTP layer, easier to reason about for simple CRUD, and has broader tooling. GraphQL shines when clients have divergent data needs (mobile vs. web), when you are aggregating multiple services, or when you want to iterate on the API without breaking consumers.


2. Explain the three root operation types in GraphQL.

Every GraphQL operation belongs to one of three types:

Query — read-only data fetching. The safe, idempotent operation.

graphql
query GetUser($id: ID!) {
  user(id: $id) {
    name
    email
    posts {
      title
    }
  }
}

Mutation — writes that change server state. Semantically equivalent to POST/PUT/PATCH/DELETE in REST.

graphql
mutation CreatePost($input: CreatePostInput!) {
  createPost(input: $input) {
    id
    title
    createdAt
  }
}

Subscription — long-lived connections for real-time data. The server pushes updates to the client when data changes. Typically implemented over WebSockets.

graphql
subscription OnNewMessage($roomId: ID!) {
  messageAdded(roomId: $roomId) {
    id
    body
    author {
      name
    }
  }
}

Important nuance: Queries can run in parallel because they are side-effect-free. Mutations in a single request run sequentially by the spec — the first mutation completes before the second starts.


3. What is a GraphQL schema and what is SDL?

The schema is the contract between client and server. It defines every type, every field on every type, and every operation the API supports. Nothing can be queried that is not in the schema.

SDL (Schema Definition Language) is the human-readable syntax for writing schemas:

graphql
type User {
  id: ID!
  name: String!
  email: String!
  role: UserRole!
  posts: [Post!]!
  createdAt: DateTime!
}

enum UserRole {
  ADMIN
  EDITOR
  VIEWER
}

type Post {
  id: ID!
  title: String!
  body: String!
  author: User!
  tags: [String!]!
  publishedAt: DateTime
}

type Query {
  user(id: ID!): User
  users(limit: Int = 10, offset: Int = 0): [User!]!
  post(id: ID!): Post
}

type Mutation {
  createPost(input: CreatePostInput!): Post!
  deletePost(id: ID!): Boolean!
}

input CreatePostInput {
  title: String!
  body: String!
  tags: [String!]
}

The ! means non-nullable. [Post!]! means the list itself is non-null and each item in the list is non-null.


4. What are resolvers and how do they work?

Resolvers are the functions that fulfill each field in the schema. When a query arrives, the GraphQL runtime walks the query tree and calls the matching resolver for each field.

Every resolver receives four arguments:

  • parent (or root) — the resolved value of the parent field
  • args — the arguments passed to this field in the query
  • context — shared data for the whole request (auth user, database connection, etc.)
  • info — metadata about the query execution (field name, return type, etc.)
javascript
const resolvers = {
  Query: {
    user: async (parent, { id }, context, info) => {
      return context.db.users.findById(id);
    },
    users: async (parent, { limit, offset }, context) => {
      return context.db.users.findAll({ limit, offset });
    },
  },

  User: {
    posts: async (parent, args, context) => {
      // parent is the User object resolved above
      return context.db.posts.findByAuthorId(parent.id);
    },
  },

  Mutation: {
    createPost: async (parent, { input }, context) => {
      if (!context.user) throw new Error('Unauthenticated');
      return context.db.posts.create({ ...input, authorId: context.user.id });
    },
  },
};

Default resolver behavior: If you do not define a resolver for a field, GraphQL uses a default resolver that simply returns parent[fieldName]. So for scalar fields on an object (like user.name), you often do not need to write a resolver at all.


5. What is the N+1 problem in GraphQL and how do you solve it?

This is one of the most common interview questions. Get it right.

The problem: Imagine you fetch a list of 10 posts and each post has an author field. Without optimization:

  1. 1One query fetches 10 posts → 1 database query
  2. 2For each post, the author resolver fires individually → 10 more queries
  3. 3Total: 11 queries when 2 would suffice

The solution: DataLoader

DataLoader batches and caches requests that happen within the same tick of the event loop.

javascript
import DataLoader from 'dataloader';

function createUserLoader(db) {
  return new DataLoader(async (userIds) => {
    const users = await db.users.findByIds(userIds);
    return userIds.map(id => users.find(u => u.id === id));
  });
}

const context = {
  db,
  loaders: {
    user: createUserLoader(db),
  },
};

const resolvers = {
  Post: {
    author: (post, args, context) => {
      return context.loaders.user.load(post.authorId);
    },
  },
};

Now 10 posts load their authors in a single SQL WHERE id IN (1,2,...,10) query.

Key points: Create DataLoader instances per request (not per server) to avoid leaking data between users. DataLoader also caches within the request. The batch function must return values in the same order as the input keys.


6. What are GraphQL variables and why should you use them?

Variables let you pass dynamic values into a query without string interpolation. They make queries reusable and prevent injection attacks.

graphql
query GetUser($id: ID!) {
  user(id: $id) {
    name
    email
  }
}

Variables are sent separately as JSON: { "id": "42" }. Variable type modifiers: String! (required), Int = 10 (optional with default), [String!] (optional array).


7. What are fragments in GraphQL?

Fragments are reusable units of fields. They prevent repeating the same field selections across multiple queries.

graphql
fragment UserBasic on User {
  id
  name
  email
  avatarUrl
}

Inline fragments (... on TypeName) are used for interface and union types to access type-specific fields in a polymorphic result.


8. What is the difference between a type and an input type?

Types (type User) are used in output — they describe what the server returns. Input types (input CreateUserInput) are used in arguments — they describe what the client sends. You cannot use a regular type as a mutation argument. Inputs are plain data containers without resolvers.


9. What are directives in GraphQL?

Directives are annotations with the @ prefix that modify execution behavior.

Built-in directives: @include(if: Boolean) — include field only if true. @skip(if: Boolean) — skip field if true. @deprecated(reason: String) — marks a schema field as deprecated.

Custom schema directives are powerful for cross-cutting concerns like auth, rate limiting, and caching without polluting resolver logic.


10. How does GraphQL handle errors?

GraphQL has a distinctive error model: a response can contain both data and errors at the same time.

json
{
  "data": { "user": { "name": "Ana Gomez", "posts": null } },
  "errors": [{ "message": "Database timeout", "path": ["user", "posts"] }]
}

Three error patterns: (1) null the field and add to errors array (default), (2) throw in a non-nullable field to bubble up to the nearest nullable ancestor, (3) union error types — model expected failures in the schema so clients know what can fail at compile time.


Section 2: Schema Design (Questions 11–18)


11. How do you design a good GraphQL schema?

Design for the client, not the database. Use strong domain-specific types — avoid JSON scalar for things that have structure. ! is a promise to the client; be careful. Use Relay connections for paginated lists. Group mutations by domain namespace to avoid a flat list of 50 mutations.


12. What are interfaces and union types? When do you use each?

Interface — defines fields implementing types must include. Use when multiple types share fields meaningful to query together (id, createdAt).

Union — a field can return one of several unrelated types. Use when types share no common fields (search results returning Users, Posts, and Products).

Querying interfaces lets you read shared fields directly; querying unions requires inline fragments for every field.


13. How does pagination work in GraphQL?

Three patterns: offset (simple, but unstable on inserts/deletes), cursor-based (recommended — opaque string, stable pages regardless of mutations), page-number (fine for admin UIs). Cursor-based pagination with the Relay connection spec (PostConnection, PostEdge, PageInfo) is the production standard.


14. How do you handle authentication and authorization in GraphQL?

Authentication happens before resolvers in the context factory — verify JWT, attach user to context. Authorization patterns: check in resolver (simple, repetitive), schema directives (declarative), or a separate permission layer like graphql-shield (most scalable for large schemas).


15. What is schema stitching vs. schema federation?

Schema Stitching: code-level composition where the gateway merges schemas programmatically. You own the merge logic. More flexible. Apollo Federation: protocol-level composition where subgraphs declare ownership via @key directives. The gateway (Apollo Router) is thin and auto-generates query plans. Use Federation for greenfield microservices with team autonomy; stitching for existing services you cannot modify.


16. What are custom scalars and when should you use them?

Built-in scalars: String, Int, Float, Boolean, ID. Custom scalars implement serialize (output), parseValue (variable input), and parseLiteral (inline literal). Use graphql-scalars for production-ready DateTime, EmailAddress, URL, UUID, JSON.


17. How do you implement soft delete vs. hard delete in a GraphQL API?

Soft delete: add deletedAt: DateTime to the type. Resolvers filter WHERE deletedAt IS NULL by default. Expose deletePost (sets timestamp) and restorePost (clears it). Both mutations return the affected object rather than a boolean for better client feedback.


18. How do you version a GraphQL API?

Do not version — evolve. Adding fields is always safe. Deprecate with @deprecated(reason: "...") and keep old fields alive during client migration. Document the sunset date in the deprecation reason. Only introduce a separate schema version for wholesale rewrites.


Section 3: Performance and Production (Questions 19–28)


19. How do you handle caching in GraphQL?

Four layers: in-resolver LRU cache for hot data, Apollo response cache plugin with @cacheControl schema directives, persisted queries for CDN GET-request cacheability, and DataLoader for per-request deduplication. Each layer addresses a different scope and granularity.


20. What is query complexity analysis and why does it matter?

Without limits, users(100) { friends(100) { friends(100) { name } } } produces 10^6 database calls. Query complexity assigns a cost per field and rejects queries exceeding a threshold. graphql-query-complexity lets you set per-field costs. graphql-depth-limit is simpler (max nesting levels) but less precise.


21. How do you implement rate limiting in GraphQL?

HTTP-level rate limiting is too blunt — one request can be cheap or catastrophically expensive. Better: track complexity budget per user per minute in Redis (INCRBY key complexity), reject when the user exceeds the budget. This ties rate limits to actual server cost.


22. What are subscriptions and how are they implemented?

Subscriptions use WebSockets (graphql-ws). The server publishes events via PubSub when mutations change data; subscribers receive updates. The in-memory PubSub is single-process only — use graphql-redis-subscriptions in production with horizontal scaling.


23. How do you secure a GraphQL API in production?

Disable introspection, add depth and complexity limits, disable unnecessary query batching, use persisted queries as a whitelist, field-level authorization, custom scalar validation, rate limiting, HTTP security headers (helmet), and strip internal error details in formatError before sending to clients.


24. How do you monitor and observe a GraphQL API?

Key metrics: operation name distribution, field usage (tracks safe deprecation), error rate per operation, resolver execution time, cache hit rate. Apollo Studio provides this out of the box. Custom plugins can instrument slow resolvers and emit histograms to any observability backend.


25. What is the difference between lazy loading and eager loading in GraphQL resolvers?

Lazy (default): each resolver fetches independently — simple but N+1 risk. Eager: parent loads related data via SQL JOIN — but wastes resources if the client did not request those fields. Correct solution: DataLoader — lazy semantics (only fetches what is requested) with eager performance (batches into one query).


26. How do you handle file uploads in GraphQL?

Use the graphql-multipart-request-spec with the Upload scalar and graphql-upload Express middleware. Validate mimetype in the resolver, then stream to S3 (or another store). In Apollo Server 3+, file upload support was removed from core and must be added explicitly.


27. What is deferred execution and the `@defer` directive?

@defer lets clients request that a fragment be streamed after the initial response via multipart HTTP. The server sends fast fields first, then deferred fragments arrive as they resolve. Use it when part of a query is expensive (AI-generated content, slow external APIs) and you want to unblock the initial render.


28. How do you implement real-time search with GraphQL?

Two approaches: debounced useLazyQuery fires a query after a delay on each keystroke — simple, no subscriptions needed. searchResults subscription pushes live results when server-side data changes — needed when results must update for all users simultaneously, not just the typing user.


Section 4: Advanced Topics (Questions 29–42)


29. How does Apollo Federation work at a technical level?

Each subgraph implements the Federation spec: @key(fields: "id") declares the entity primary key, __resolveReference resolves the entity from that key. The router builds a query plan tree, executes fetches to relevant subgraphs (in parallel where possible), and merges results into one response. Subgraphs extend each other's types via extend type User @key(fields: "id") + @external.


30. What is the difference between schema-first and code-first approaches?

Schema-first: SDL is the source of truth — write schema, implement resolvers. Good for team collaboration; needs graphql-codegen for full type safety. Code-first (TypeGraphQL, Nexus): define types programmatically, SDL is generated from code — native TypeScript type safety without code generation, but schema-as-documentation is less natural.


31. How does `graphql-codegen` work and why is it important?

It reads your schema and .graphql operation files, then generates TypeScript types, input types, and React hooks. Your components get full type safety for query variables and response shapes without writing types manually. Run it in watch mode during development and as part of CI.


32. What are GraphQL persisted queries and automatic persisted queries (APQ)?

Persisted queries: pre-register queries at build time, clients send only the hash — smaller payloads, CDN GET cacheability, and a whitelist security model. APQ: a negotiation protocol — client sends hash first; on cache miss, server requests full query, stores it, and responds. Future requests use the hash only.


33. How do you test GraphQL resolvers?

Unit: call resolver functions directly with a mocked context — fast and isolated. Integration: use server.executeOperation() with a real schema and test database — exercises the full resolver tree without HTTP overhead. Always test error paths: unauthenticated access, not-found inputs, and validation failures.


34. How does the GraphQL execution algorithm work internally?

Parse (query string → AST), Validate (AST against schema — rejects invalid queries before any resolver runs), Execute (walk the AST, call resolvers, await Promises), Coerce (serialize scalars for the response). Queries resolve fields in parallel; top-level mutation fields run sequentially per spec.


35. What is the `context` object and what should go in it?

Context is a per-request dependency injection container. Put in: authenticated user, DB connection, DataLoader instances (per-request), external service clients, request-scoped logger, feature flags. Do not put in: state only one resolver needs, mutable state you modify mid-execution.


36. What are the key differences between Apollo Client and urql?

Apollo Client: larger ecosystem, normalized InMemoryCache, Apollo Studio integration, ~32 KB bundle. urql: composable exchange pipeline, optional normalized cache, ~7 KB bundle, simpler defaults. Pick Apollo for enterprise apps or Federation; pick urql for smaller projects where bundle size matters or you want full control.


37. How does the InMemoryCache work in Apollo Client?

It normalizes data into a flat store keyed by TypeName:id. Updating User:42 in a mutation automatically propagates to every query that reads User:42. Use cache.modify() for manual updates when adding or removing items from a list — Apollo cannot infer list changes automatically.


38. What is optimistic UI and how do you implement it in Apollo Client?

Provide an optimisticResponse in useMutation. Apollo writes it to cache immediately; the UI updates before the server responds. When the real response arrives, it replaces the optimistic data. If the server errors, Apollo reverts to the pre-mutation state automatically.


39. Explain the `@defer` and `@stream` directives.

@defer delays an entire fragment. @stream delivers list items incrementally as they resolve. Both use multipart HTTP or SSE. Supported by Apollo Router, GraphQL Yoga, and Mercurius. Use @stream for long lists where early items should render before the full list is ready.


40. How do you implement a connection between two existing services using GraphQL federation?

User service: type User @key(fields: "id") { id name email }. Order service: extend type User @key(fields: "id") { id: ID! @external; orders: [Order!]! } plus __resolveReference({ id }) { return { id } }. The router's query plan automatically fetches user fields from user-service and order fields from order-service, then merges the response.


41. What are some common GraphQL anti-patterns to avoid?

Mirroring the database schema, overusing JSON scalar, marking every field non-null, giant flat mutation lists, resolvers doing business logic (put that in a service layer), missing DataLoader on any list relationship, leaking internal errors to clients, removing deprecated fields without a migration window.


42. How would you migrate a REST API to GraphQL incrementally?

Phase 1: GraphQL facade over REST — resolvers call existing REST endpoints, zero backend changes, validate the approach. Phase 2: migrate high-value resolvers to hit the DB directly. Phase 3: new services expose GraphQL natively, facade thins out. Phase 4: decommission REST routes once no consumers remain. Never force a hard cutover.


Section 5: Interview Meta-Questions (Questions 43–45)


43. When should you NOT use GraphQL?

Simple CRUD with one client, APIs requiring aggressive HTTP caching without APQ setup, file-heavy transfer scenarios better served by presigned S3 URLs, simple public APIs consumed by many external parties (REST is more universally understood), and small teams moving fast where the schema design overhead outweighs the benefits.


44. What is your process for debugging a slow GraphQL query?

Enable tracing to find slow resolvers. Check for N+1. Inspect generated SQL and missing indexes. Check whether resolvers load data the client did not request. Profile for external API latency vs. DB latency. Consider resolver-level TTL caching. Consider field-level pagination if returning huge lists.


45. How would you explain GraphQL to a non-technical stakeholder?

"Think of REST like a fixed combo meal — you get everything on the plate whether you want it or not. GraphQL is ordering a la carte: you tell the kitchen exactly what you want and that is exactly what arrives. Our mobile app gets a lightweight response; our web app gets full detail — from the same endpoint, without maintaining two separate APIs."


Quick Reference Cheat Sheet

Core concepts: Schema = contract, Resolver = field fetcher, Context = per-request DI container, DataLoader = batch + cache per request.

Performance checklist: DataLoader for all relationship fields, query complexity + depth limits, disable introspection in production, response caching for public queries, persisted queries for CDN cacheability.

Error handling: Union types for expected errors, thrown errors for unexpected ones, never expose internals in production.

Schema design: Design for clients not databases, non-null is a promise so use carefully, use connections for paginated lists, deprecate before removing fields.

Testing: Unit test resolvers with mocked context, integration test with executeOperation (no HTTP layer needed), always test error paths.


Closing Thoughts

The engineers who do best in GraphQL interviews are the ones who have genuinely built something with it and hit the hard edges — the N+1 problem on a real database, a subscription that leaked memory because the DataLoader was created at server start, a union type that needed a __resolveType function. Study this guide, then build something. The questions above will feel familiar when you have debugged them in production.

FAQ

What is GraphQL and how does it differ from REST?+

GraphQL is a query language for APIs where the client specifies exactly what data it needs. Unlike REST (which has multiple endpoints and returns fixed data shapes), GraphQL uses a single endpoint and lets clients request only the fields they need. This eliminates over-fetching (getting too much data) and under-fetching (needing multiple requests to get enough data).

What is the N+1 problem and how does DataLoader solve it?+

The N+1 problem occurs when fetching a list of N items triggers N additional queries to resolve a related field. For example, fetching 10 posts and then firing a separate database query for each post's author = 11 queries total. DataLoader solves this by batching all those individual lookups into a single query (WHERE id IN (...)) within the same event loop tick, and caching results within the request.

What are the three GraphQL operation types?+

Query (read-only data fetching, side-effect free, fields resolve in parallel), Mutation (writes that change server state, top-level fields run sequentially per spec), and Subscription (long-lived real-time connections via WebSockets where the server pushes updates when data changes).

How does GraphQL handle errors differently from REST?+

In GraphQL, a response can contain both data and errors simultaneously. If one resolver fails, the rest of the query can still succeed. The failed field becomes null and an entry appears in the errors array. This contrasts with REST where a 500 means the entire response failed. For expected business errors, the best practice is to model them as union types in the schema rather than thrown exceptions.

What is Apollo Federation and when would you use it?+

Apollo Federation is a protocol for composing multiple independent GraphQL services (subgraphs) into a single unified API. Each subgraph owns its types and declares them with @key directives. A gateway (Apollo Router) queries the relevant subgraphs and merges results. Use it when you have multiple teams owning different parts of your data graph and you want service autonomy without exposing multiple endpoints to clients.

How do you secure a GraphQL API in production?+

Key measures: disable introspection so the schema is not publicly browsable, implement query depth limiting and complexity analysis to prevent expensive queries, use per-user rate limiting based on query complexity rather than just request count, implement field-level authorization in resolvers or via schema directives, use persisted queries to whitelist known operations, never expose internal error details to clients, and apply standard HTTP security headers.

What is the difference between an interface and a union type in GraphQL?+

Interfaces define a set of fields that all implementing types must include — use them when types share common fields you want to query without inline fragments (like id or createdAt). Union types group unrelated types that can appear in the same field — use them when the types share no meaningful common fields, like search results that can be Users, Posts, or Products.

What is schema-first vs. code-first development in GraphQL?+

Schema-first: you write SDL (Schema Definition Language) files first, then implement resolvers that match the schema. The SDL is the source of truth. Code-first: you define types programmatically using decorators or builders (TypeGraphQL, Nexus), and SDL is generated from the code. Schema-first makes the schema a shareable artifact good for team collaboration; code-first provides native TypeScript type safety without code generation.

Artículos relacionados

How to Answer Conflict-With-a-Coworker Interview Questions

Learn how to answer conflict-with-a-coworker interview questions with real examples and proven techniques. Stand out in tech and remote job interviews.

How to Answer 'Why Do You Want to Work Here' in Interviews

Discover expert strategies for answering 'why do you want to work here,' tailored for remote tech roles and dollar opportunities. Real, practical interview tips.

Frontend Developer Interview Questions and How to Answer Them (50+)

Complete SEO article covering 54 frontend developer interview questions with detailed answers, real code snippets across HTML, CSS, JavaScript, React, TypeScript, accessibility, security, build tools, and testing.

Full-Stack Developer Interview Questions: How to Answer Like a Pro (45+)

Comprehensive full-stack developer interview guide with 46 numbered questions covering JavaScript/TypeScript, React, CSS, REST APIs, databases, Node.js, system design, security, testing, DevOps, and advanced architecture topics. Each answer includes working code examples and production-level context.

Preparate para tu entrevista real

Pegá el link de tu vacante: investigamos quién te entrevista y te ensayamos en vivo.

Empezar gratis →

¿Tenés entrevista próxima? Instalá el copiloto en vivo →

InterviewHack.ai

Preparate para la entrevista exacta: quién te entrevista, tu CV a medida y coach real.

Producto

VacantesRevisar CV (ATS) gratis¿Cómo suena tu inglés?¿Te pagan bien?Reporte de sueldos LATAMCursos gratisBlogCV a medidaPráctica habladaEs gratis

Empleos remotos

ReactPythonFull-StackLATAMArgentinaMéxicoVer todas →

Preparate

Práctica habladaFrontendBackendAI EngineerPor empresaVendete con tu CV

Empresa

Buscás talentoAcerca deContactoPrivacidadTérminos

© 2026 InterviewHack.ai · Tu CV es tuyo. Nunca se usa para entrenar nada. · Un producto de IA-PTY