Skip to content

Quiz: GraphQL

← Back to quiz index

6 questions

L1 (4 questions)

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

Show answer When a query requests a list of items with nested relationships (e.g., users and their posts), a naive resolver fetches each user, then makes a separate database query per user for their posts — 1 query for users + N queries for posts. Solution: use a DataLoader (batching library) that collects all requested IDs in a single tick, then makes one batch query (WHERE user_id IN (...)). DataLoader also deduplicates repeated IDs. Without it, a single GraphQL query can generate hundreds of database queries.

2. How do you prevent a malicious or careless client from sending an extremely expensive GraphQL query?

Show answer 1. Query depth limiting — reject queries nested deeper than N levels.
2. Query complexity analysis — assign cost to each field and reject queries exceeding a budget.
3. Timeout — abort queries running longer than a threshold.
4. Persisted queries — clients can only send pre-approved query hashes, not arbitrary queries.
5. Rate limiting per client. Use a combination: depth + complexity + timeout for internal APIs, persisted queries for public APIs.

3. What are GraphQL subscriptions and how do they work under the hood?

Show answer Subscriptions allow clients to receive real-time updates when data changes. The client sends a subscription query over a persistent connection (typically WebSocket using graphql-ws protocol). The server pushes events matching the subscription filter. Implementation: the server maintains a pub/sub system — when a mutation occurs, it publishes an event, and the subscription resolver filters and delivers it to connected clients. Use for: live dashboards, chat, notifications. Do not use for polling-replaceable use cases — subscriptions have higher server cost per connection.

4. What is the difference between a GraphQL query, mutation, and the resolver pattern?

Show answer Queries read data (GET equivalent), mutations modify data (POST/PUT/DELETE equivalent). Both are defined in the schema and backed by resolvers — functions that fetch or modify the data for each field. The resolver chain: a top-level resolver fetches the root object, then child resolvers fetch nested fields. Each resolver receives (parent, args, context, info). Context carries request-scoped data (auth, DataLoader instances). Keep resolvers thin — delegate business logic to service layers.

L2 (2 questions)

1. What is the difference between schema-first and code-first GraphQL development, and when would you choose each?

Show answer Schema-first: write the .graphql SDL schema file first, then implement resolvers to match it. Benefits: schema is the contract, non-developers can review it, great for API-first design. Drawbacks: schema and code can drift. Code-first: define the schema programmatically in code (e.g., using decorators or builder patterns). Benefits: single source of truth, type-safe, no drift. Drawbacks: harder for non-developers to review. Choose schema-first for multi-team APIs where the schema is a shared contract. Choose code-first for single-team rapid iteration.

2. How do you implement pagination in GraphQL and what is Relay-style cursor pagination?

Show answer Two approaches: offset-based (skip/limit — simple but breaks with concurrent inserts and is slow for large offsets) and cursor-based (encode a pointer into the result set). Relay-style cursor pagination uses a connection pattern: query returns edges (node + cursor) and pageInfo (hasNextPage, endCursor). Client passes 'after: cursor, first: N' for the next page. The cursor is typically a base64-encoded primary key or timestamp. Cursor-based is more stable and performant for large datasets.