Introduction
GraphQL has gone from a Facebook internal tool to the API layer of choice for product-driven companies that want to empower frontend teams. GitHub's public API, Shopify's Storefront API, Twitter's internal graph, and hundreds of others are built on GraphQL. If you are interviewing at any company with a complex frontend or mobile product, GraphQL questions are increasingly common.
Core Concepts
Q: What is GraphQL and how does it differ from REST?
GraphQL is a query language and runtime for APIs that allows clients to request exactly the data they need—no more, no less.
| REST | GraphQL | |
|---|---|---|
| Endpoint structure | Multiple resource endpoints | Single endpoint (/graphql) |
| Response shape | Server-defined | Client-defined |
| Over-fetching | Common | Eliminated |
| Under-fetching | Requires N+1 requests | Solved with nested queries |
| Versioning | URL versioning (/v2/) | Schema evolution (deprecations) |
| Tooling | OpenAPI/Swagger | Introspection, GraphiQL, Apollo Studio |
Q: What are the three root types in GraphQL?
- Query: Read operations. The equivalent of GET in REST.
- Mutation: Write operations (create, update, delete). The equivalent of POST/PUT/DELETE.
- Subscription: Real-time event streams delivered via WebSockets. The equivalent of Server-Sent Events or WebSocket endpoints in REST.
Schema Design
Q: How do you design a GraphQL schema for a social media platform?
type User {
id: ID!
username: String!
email: String!
posts(first: Int, after: String): PostConnection!
followers(first: Int): UserConnection!
}
type Post {
id: ID!
content: String!
author: User!
likes: Int!
comments(first: Int, after: String): CommentConnection!
createdAt: DateTime!
}
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
}
type PageInfo {
hasNextPage: Boolean!
endCursor: String
}
Key design decisions: Use the Relay Cursor Connection Specification for paginated lists (enables efficient cursor-based pagination). Use ! to denote non-nullable fields (fail fast, avoid null propagation). Model relationships as nested types, not foreign key IDs.
Q: What is input type and when should you use it?
Input types are special object types used exclusively as mutation arguments. They avoid bloated argument lists and allow reuse across mutations:
input CreatePostInput {
content: String!
tags: [String!]
scheduledAt: DateTime
}
type Mutation {
createPost(input: CreatePostInput!): Post!
updatePost(id: ID!, input: CreatePostInput!): Post!
}
The N+1 Problem & DataLoader
Q: What is the N+1 problem in GraphQL and how do you solve it?
The N+1 problem occurs when resolving a list of items requires 1 query for the list plus N individual queries for each item's nested fields.
query {
posts { # 1 query: SELECT * FROM posts
author { # N queries: SELECT * FROM users WHERE id =? (once per post)
username
}
}
}
For 100 posts, this generates 101 database queries.
Solution: DataLoader (Facebook's batching library, available in all major GraphQL server frameworks).
DataLoader collects all the user IDs requested during a single tick of the event loop, then fetches them in a single batched query:
const userLoader = new DataLoader(async (userIds) => {
// Called ONCE with all userIds collected in this tick
const users = await db.users.findMany({ where: { id: { in: userIds } } });
return userIds.map(id => users.find(u => u.id === id));
});
// In your resolver:
const authorResolver = (post, _, { loaders }) => {
return loaders.user.load(post.authorId); // batched automatically
};
Result: 100 posts + 1 batched user query = 2 queries total.
Authentication & Authorization
Q: How do you handle authentication and authorization in GraphQL?
Authentication (who are you?) is typically handled at the HTTP layer—JWT tokens in the Authorization header, validated in middleware before the GraphQL layer.
Authorization (what can you access?) is more nuanced in GraphQL because a single request can touch many resources. Two common patterns:
- Resolver-level authorization: Each resolver checks permissions before returning data.
const postResolver = async (_, { id }, { currentUser }) => {
const post = await db.posts.findById(id);
if (!currentUser || post.authorId!== currentUser.id) {
throw new ForbiddenError("Not authorized");
}
return post;
};
- Schema directives:
@authdirectives applied at the schema level using libraries likegraphql-shieldor custom directive transformers. Cleaner, but requires careful implementation to avoid bypasses.
Performance & Caching
Q: How do you cache GraphQL responses?
REST caching is straightforward (HTTP cache, URL-based). GraphQL is harder because:
- All requests hit the same endpoint (
/graphql). - POST requests are not cached by default in HTTP caches.
Strategies:
- Per-resolver caching: Cache expensive resolver results in Redis with a TTL.
- Persisted queries: Pre-register known queries; use GET requests with a query ID. Enables CDN caching.
- Response caching: Libraries like Apollo Server's
@cacheControldirective hint at cache TTLs. - CDN caching with GET: Apollo Client and Relay can use GET requests for queries, enabling CDN caching.
Q: What is query complexity analysis and why is it important?
Without complexity limits, a malicious or naive client can craft a deeply nested query that generates millions of database queries:
{ user { friends { friends { friends { friends { posts { comments { author { name } } } } } } } } }
Implement query depth limiting and complexity scoring. Assign a cost to each field and reject queries that exceed a threshold. Libraries: graphql-depth-limit, graphql-query-complexity.
GraphQL Federation
Q: What is Apollo Federation and what problem does it solve?
Federation allows multiple independent GraphQL services to compose into a single supergraph—enabling microservices architecture with a unified API gateway.
Each subgraph owns its domain (Users, Products, Orders). The Apollo Router at the gateway level merges schemas and routes fields to the appropriate subgraph.
# Users subgraph - owns User type
type User @key(fields: "id") {
id: ID!
username: String!
}
# Orders subgraph - extends User with orders
extend type User @key(fields: "id") {
id: ID! @external
orders: [Order!]!
}
Benefits: Independent deployment, team ownership boundaries, no shared schema monolith. This is the architecture used by large-scale GraphQL deployments at Expedia, Netflix, and others.
Summary
GraphQL interviews reward engineers who think about client needs, API evolution, and performance at scale. Master the N+1 problem with DataLoader, understand the tradeoffs of federation vs. monolithic schemas, and demonstrate that you can design schemas that scale with a product.
