SQL database resolvers in GraphQL - Time & Space Complexity
When using SQL database resolvers in GraphQL, it's important to understand how the time to get data grows as the data size grows.
We want to know how the number of database calls and data processing steps increase when we ask for more or bigger data.
Analyze the time complexity of the following GraphQL resolver code that fetches data from an SQL database.
query GetUsersWithPosts {
users {
id
name
posts {
id
title
}
}
}
This query fetches a list of users and for each user, it fetches their posts from the database.
Look for repeated database calls or loops in the resolver process.
- Primary operation: Fetching posts for each user, often done with one query per user.
- How many times: Once for each user in the users list.
As the number of users grows, the number of post-fetching operations grows too.
| Input Size (n users) | Approx. Operations (queries) |
|---|---|
| 10 | 1 (users) + 10 (posts queries per user) = 11 |
| 100 | 1 + 100 = 101 |
| 1000 | 1 + 1000 = 1001 |
Pattern observation: The number of operations grows roughly in a straight line with the number of users.
Time Complexity: O(n)
This means the time to get all users and their posts grows directly in proportion to the number of users.
[X] Wrong: "Fetching posts for all users is just one database call, so time stays the same no matter how many users."
[OK] Correct: Usually, fetching posts for each user requires separate calls or processing, so time grows with the number of users.
Understanding how database resolvers scale helps you write efficient queries and explain your choices clearly in interviews.
"What if we changed the resolver to fetch all posts for all users in a single query? How would the time complexity change?"
Practice
Solution
Step 1: Understand the purpose of resolvers
Resolvers are functions that connect GraphQL queries or mutations to the actual data source, here an SQL database.Step 2: Identify the role in data fetching and modification
Resolvers run SQL commands to get or change data as requested by the GraphQL operations.Final Answer:
To fetch or modify data in the SQL database based on GraphQL queries or mutations -> Option DQuick Check:
Resolvers connect queries to SQL data [OK]
- Confusing resolvers with schema definitions
- Thinking resolvers handle authentication
- Assuming resolvers format output only
getUser that fetches user data from an SQL database?Solution
Step 1: Identify the correct resolver structure for a query
Resolvers for queries are defined inside theQueryobject with function values.Step 2: Check the syntax for calling the database query
The resolver should be a function returning the result ofdb.querywith the SQL command string.Final Answer:
const resolvers = { Query: { getUser: () => db.query('SELECT * FROM users') } }; -> Option AQuick Check:
Query resolvers are functions inside Query object [OK]
- Placing query resolver inside Mutation
- Assigning SQL string directly instead of a function
- Defining resolver outside Query or Mutation objects
{ getUserById(id: 2) { name } } is executed?const resolvers = {
Query: {
getUserById: (_, args) => {
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Carol' }
];
return users.find(user => user.id === args.id);
}
}
};Solution
Step 1: Understand the resolver logic
The resolver searches the users array for an object withidequal toargs.id.Step 2: Match the query argument with the array data
The query requestsid: 2, so the resolver returns the user withid: 2, which is { id: 2, name: 'Bob' }.Final Answer:
{ "data": { "getUserById": { "name": "Bob" } } } -> Option BQuick Check:
Find user with id 2 = Bob [OK]
- Confusing id 2 with id 1 or 3
- Forgetting to access args.id
- Assuming resolver returns entire user object without filtering
const resolvers = {
Mutation: {
addUser: (parent, args) => {
db.query(`INSERT INTO users (name, email) VALUES (${args.name}, ${args.email})`);
return { success: true };
}
}
};Solution
Step 1: Analyze the SQL query construction
The SQL command uses string interpolation to insertargs.nameandargs.emaildirectly into the query string.Step 2: Identify security risk
This approach allows SQL injection attacks if user input contains malicious SQL code. Proper parameterization or prepared statements are needed.Final Answer:
SQL injection risk due to string interpolation without parameterization -> Option AQuick Check:
Unsafe string interpolation causes SQL injection [OK]
- Ignoring SQL injection vulnerability
- Thinking backticks cause errors
- Expecting mutation in Query object
users(id, active) and orders(id, user_id, total). Which resolver implementation correctly enforces this condition efficiently?Solution
Step 1: Understand the requirement
Fetch orders only if the user is active, combining the check and data fetch efficiently.Step 2: Evaluate each option
const resolvers = { Query: { userOrders: (_, args) => db.query(`SELECT * FROM orders WHERE user_id = ${args.userId} AND EXISTS (SELECT 1 FROM users WHERE id = ${args.userId} AND active = TRUE)`) } }; uses a single SQL query withEXISTSto check user active status and fetch orders in one call, which is efficient and correct.
const resolvers = { Query: { userOrders: (_, args) => { const user = db.query(`SELECT * FROM users WHERE id = ${args.userId}`); if (user.active) { return db.query(`SELECT * FROM orders WHERE user_id = ${args.userId}`); } else { return []; } } } }; fetches user first then orders separately, causing multiple queries and possible async issues.
const resolvers = { Query: { userOrders: (_, args) => db.query(`SELECT * FROM orders WHERE user_id = ${args.userId}`) } }; fetches orders without checking user active status.
const resolvers = { Query: { userOrders: (_, args) => db.query(`SELECT * FROM users WHERE id = ${args.userId} AND active = TRUE`) } }; fetches user data only, not orders.Final Answer:
const resolvers = { Query: { userOrders: (_, args) => db.query(`SELECT * FROM orders WHERE user_id = ${args.userId} AND EXISTS (SELECT 1 FROM users WHERE id = ${args.userId} AND active = TRUE)`) } }; -> Option CQuick Check:
Single query with EXISTS enforces condition efficiently [OK]
- Running multiple queries instead of one
- Not checking user active status before fetching orders
- Returning user data instead of orders
Ready to prove it?
Pass the test and this pattern moves unknown
