GraphQL Interview Questions: How to Survive (and Thrive)

You've got a GraphQL interview tomorrow. Panic is setting in. Fear not - this is your survival guide. From basic concepts to system design, I've been on both sides of the table, and these are the questions that matter.
The Warm-Up Questions
Every interview starts here. Get these wrong, and you won't make it to the fun stuff.
Q: What is GraphQL?
Bad answer: "It's like REST but better."
Good answer: "GraphQL is a query language for APIs and a runtime for executing those queries. It lets clients request exactly the data they need, no more, no less. Unlike REST where the server defines response structure, GraphQL puts clients in control of data selection."
Great answer: Add this: "It was developed by Facebook in 2012, open-sourced in 2015, and has become particularly valuable for mobile applications where bandwidth is limited and for complex applications with varied data requirements."
Q: What are the core operations in GraphQL?
# Query - Read data
query {
user(id: "123") { name }
}
# Mutation - Write data
mutation {
createUser(name: "Jane") { id }
}
# Subscription - Real-time updates
subscription {
messageAdded { content }
}
Q: Explain the GraphQL type system.
# Scalar types - primitives
Int, Float, String, Boolean, ID
# Object types - custom types
type User {
id: ID!
name: String!
}
# Input types - for arguments
input CreateUserInput {
name: String!
email: String!
}
# Enum types
enum Status { ACTIVE INACTIVE }
# Interface types
interface Node { id: ID! }
# Union types
union SearchResult = User | Product | Article
The "I've Used GraphQL" Questions
These separate tourists from residents.
Q: What is the N+1 problem and how do you solve it?
The problem:
query {
users { # 1 query
name
posts { # N queries (one per user)
title
}
}
}
The solution: DataLoader for batching and caching.
const userLoader = new DataLoader(userIds => {
return db.users.findMany({ where: { id: { in: userIds } } });
});
// Instead of N queries, one batched query:
// SELECT * FROM users WHERE id IN (1, 2, 3, ...)
Q: What's the difference between nullable and non-nullable types?
type User {
id: ID! # Non-null - must always have a value
name: String! # Non-null
nickname: String # Nullable - can be null
}
Key insight: "If a non-null field resolves to null, the error propagates up to the nearest nullable parent. This can null out entire branches of your response."
Q: How would you handle authentication in GraphQL?
// Context-based approach
@Bean
public WebGraphQlInterceptor authInterceptor() {
return (request, chain) -> {
String token = request.getHeaders().getFirst("Authorization");
User user = authService.validateToken(token);
request.configureExecutionInput((input, builder) ->
builder.graphQLContext(ctx -> ctx.put("currentUser", user)).build()
);
return chain.next(request);
};
}
// Use in resolvers
@QueryMapping
public User me(DataFetchingEnvironment env) {
User current = env.getGraphQlContext().get("currentUser");
if (current == null) throw new UnauthorizedException();
return current;
}
Q: How do you handle authorization at the field level?
// Method-level security
@SchemaMapping(typeName = "User", field = "salary")
@PreAuthorize("hasRole('HR') or #user.id == authentication.name")
public BigDecimal salary(User user) {
return user.getSalary();
}
// Or custom logic
@SchemaMapping(typeName = "User", field = "privateEmail")
public String privateEmail(User user, DataFetchingEnvironment env) {
User current = env.getGraphQlContext().get("currentUser");
if (!user.getId().equals(current.getId()) && !current.isAdmin()) {
return null; // Or throw ForbiddenException
}
return user.getPrivateEmail();
}
The "I've Designed GraphQL APIs" Questions
Now it gets interesting.
Q: How would you design pagination for a GraphQL API?
Offset pagination (simple):
type Query {
users(page: Int, pageSize: Int): UserPage!
}
type UserPage {
items: [User!]!
totalCount: Int!
hasNextPage: Boolean!
}
Cursor pagination (scalable):
type Query {
users(first: Int, after: String): UserConnection!
}
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
}
type UserEdge {
node: User!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
Why cursor pagination? "Offset pagination breaks when data changes between requests. If a user is deleted while you're on page 2, page 3's offset shifts and you'll skip or duplicate items. Cursors are stable pointers that survive data changes."
Q: How would you handle file uploads?
Options:
- Separate REST endpoint (recommended)
// REST for upload
POST /upload → returns fileId
// GraphQL for metadata
mutation {
attachFile(documentId: "123", fileId: "456") {
document { attachments { url } }
}
}
- GraphQL multipart spec
scalar Upload
mutation UploadFile($file: Upload!) {
uploadFile(file: $file) {
url
size
}
}
Honest answer: "File uploads in GraphQL are awkward. I'd recommend a separate REST endpoint for the actual upload, then reference the uploaded file in GraphQL mutations."
Q: How do you version a GraphQL API?
Trick question! GraphQL is designed to avoid versioning.
type User {
# Deprecated - will be removed in Q3 2024
fullName: String @deprecated(reason: "Use firstName and lastName instead")
# New fields
firstName: String!
lastName: String!
}
Key points:
- Add fields freely (non-breaking)
- Deprecate fields, don't remove immediately
- Use
@deprecateddirective - Monitor deprecated field usage before removal
- If you must version: URL path (
/graphql/v2) as last resort
The System Design Questions
Q: Design a GraphQL API for a social media platform.
Think out loud:
type Query {
# Entry points
me: User
user(id: ID!): User
feed(first: Int, after: String): PostConnection!
search(query: String!, type: SearchType): SearchResultConnection!
}
type Mutation {
createPost(input: CreatePostInput!): Post!
likePost(postId: ID!): Post!
followUser(userId: ID!): User!
sendMessage(input: SendMessageInput!): Message!
}
type Subscription {
newFeedPost: Post!
newMessage(conversationId: ID!): Message!
newNotification: Notification!
}
type User @key(fields: "id") {
id: ID!
username: String!
displayName: String!
avatar: String
bio: String
posts(first: Int, after: String): PostConnection!
followers(first: Int, after: String): UserConnection!
following(first: Int, after: String): UserConnection!
isFollowedByMe: Boolean!
}
type Post @key(fields: "id") {
id: ID!
author: User!
content: String!
media: [Media!]!
likes: Int!
isLikedByMe: Boolean!
comments(first: Int, after: String): CommentConnection!
createdAt: DateTime!
}
Address scalability:
- "For
feed, I'd use cursor pagination with a pre-computed feed per user." - "For
isFollowedByMe, I'd batch-load using DataLoader with the viewer's ID." - "Subscriptions would use Redis pub/sub for horizontal scaling."
Q: How would you handle a GraphQL API that's too slow?
┌─────────────────────────────────────────────────────────────────────┐
│ PERFORMANCE TROUBLESHOOTING │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. IDENTIFY THE BOTTLENECK │
│ □ Add tracing (Apollo Studio, custom instrumentation) │
│ □ Identify slow resolvers │
│ □ Check database query counts │
│ │
│ 2. COMMON FIXES │
│ □ DataLoader for N+1 issues │
│ □ Database indexes for slow queries │
│ □ Caching (response, field-level, normalized) │
│ □ Pagination limits │
│ □ Query complexity limits │
│ │
│ 3. ADVANCED FIXES │
│ □ Persisted queries │
│ □ CDN caching with GET requests │
│ □ Federation for horizontal scaling │
│ □ Defer/stream for large responses │
│ │
└─────────────────────────────────────────────────────────────────────┘
The Tricky Questions
Q: When would you NOT use GraphQL?
Good answers:
- "Simple CRUD with predictable access patterns - REST is simpler"
- "File upload heavy apps - GraphQL handles files awkwardly"
- "Public APIs where HTTP caching is critical"
- "Small teams where GraphQL's flexibility isn't worth the overhead"
- "Real-time only apps - might just use WebSockets directly"
Q: What's the biggest mistake you've made with GraphQL?
Be honest. Examples:
- "Didn't implement query complexity limits. Someone ran a query that took down the database."
- "Made nullable fields non-null in a schema migration. Broke 40% of queries."
- "Forgot DataLoader. Had a resolver making 10,000 database calls."
- "Over-engineered the schema before understanding requirements."
Q: How do you test GraphQL APIs?
// Unit test a resolver
@Test
void shouldReturnUserById() {
when(userRepository.findById("123"))
.thenReturn(Optional.of(new User("123", "Jane")));
User result = userController.user("123");
assertThat(result.getName()).isEqualTo("Jane");
}
// Integration test with GraphQlTester
@SpringBootTest
@AutoConfigureGraphQlTester
class UserIntegrationTest {
@Autowired
GraphQlTester graphQlTester;
@Test
void shouldQueryUser() {
graphQlTester.document("""
query {
user(id: "123") {
name
email
}
}
""")
.execute()
.path("user.name").entity(String.class).isEqualTo("Jane")
.path("user.email").entity(String.class).isEqualTo("jane@example.com");
}
}
The "Culture Fit" Questions
Q: How do you handle disagreements about schema design?
Framework:
- "I start with use cases, not opinions. What queries will clients actually run?"
- "I create multiple options and evaluate trade-offs."
- "I write down decisions in ADRs (Architecture Decision Records)."
- "If we can't agree, I defer to data: let's measure after shipping both options."
Q: How do you stay updated on GraphQL best practices?
Real answers:
- GraphQL Weekly newsletter
- Apollo blog
graphql-specGitHub discussions- Conference talks (GraphQL Summit, etc.)
- Building side projects
Quick-Fire Round
Q: ID vs String?
"Use ID for identifiers. It signals intent, even though it serializes to string."
Q: When to use interfaces vs unions? "Interfaces when types share fields. Unions when they don't."
Q: Biggest advantage over REST? "Clients request exactly what they need. No over-fetching, no under-fetching."
Q: Biggest disadvantage? "Caching is harder. HTTP caching doesn't work out of the box."
Q: Favorite GraphQL tool? (Have an answer. Shows you actually use GraphQL.)
The Night Before Checklist
□ Can explain GraphQL to a non-technical person
□ Know the N+1 problem and DataLoader solution
□ Understand nullability and error propagation
□ Can design a schema for a given domain
□ Know authentication and authorization patterns
□ Understand pagination approaches
□ Can discuss trade-offs (GraphQL vs REST)
□ Have war stories (things that went wrong and how you fixed them)
□ Can whiteboard a system design with GraphQL
□ Have questions to ask the interviewer
Questions to Ask Them
- "What does your GraphQL architecture look like? Monolith or federated?"
- "How do you handle schema changes and deprecations?"
- "What's your biggest GraphQL pain point right now?"
- "How do you monitor GraphQL performance in production?"
- "What would I be working on in my first 90 days?"
Good luck tomorrow. Remember: interviewers want you to succeed. They're not trying to trick you - they're trying to find a collaborator.
Take a breath. Trust your preparation. You've got this.
And if they ask something you don't know? "I don't know, but here's how I'd figure it out" is always a valid answer.