Interview

20 GraphQL Interview Questions and Answers

Prepare for your interview with this guide on GraphQL, covering key concepts and best practices to help you demonstrate your proficiency.

GraphQL has emerged as a powerful alternative to REST for API development, offering a more efficient and flexible approach to querying data. By allowing clients to request exactly the data they need, GraphQL minimizes over-fetching and under-fetching issues, making it a preferred choice for modern web and mobile applications. Its strong type system and introspective capabilities further enhance its robustness and ease of use.

This article provides a curated selection of GraphQL interview questions designed to help you demonstrate your proficiency and understanding of this technology. Reviewing these questions will prepare you to discuss key concepts, best practices, and real-world applications of GraphQL, ensuring you are well-equipped for your upcoming interview.

GraphQL Interview Questions and Answers

1. What is a GraphQL Schema?

A GraphQL schema serves as the blueprint for your API, defining data types and their relationships. It uses the GraphQL Schema Definition Language (SDL) to specify queries, mutations, and subscriptions.

Example:

type Query {
    book(id: ID!): Book
    books: [Book]
}

type Mutation {
    addBook(title: String!, author: String!): Book
}

type Book {
    id: ID!
    title: String!
    author: String!
}

In this schema, the Query type includes book and books fields, while the Mutation type has an addBook field. The Book type consists of id, title, and author fields.

2. How do you define a query?

A GraphQL query requests specific data from a server, allowing you to fetch exactly what you need in a single request. This enhances data fetching efficiency.

Example:

{
  user(id: "1") {
    id
    name
    email
  }
}

This query requests the id, name, and email of a user with ID “1”, and the server responds with the requested data.

3. How can you handle errors?

GraphQL handles errors by returning an errors array in the response, detailing issues encountered. This allows clients to understand what went wrong while still receiving partial data if available.

Example:

const { ApolloServer, gql } = require('apollo-server');

const typeDefs = gql`
  type Query {
    hello: String
  }
`;

const resolvers = {
  Query: {
    hello: () => {
      try {
        throw new Error('Something went wrong!');
      } catch (error) {
        return new Error('Custom error message');
      }
    },
  },
};

const server = new ApolloServer({ typeDefs, resolvers });

server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

In this example, the resolver for the hello field throws an error, which is caught and returned as a custom error message.

4. Describe how to use variables in a query.

Variables in GraphQL queries allow for parameterization, making them dynamic and reusable. Instead of hardcoding values, you define variables with a $ prefix, specify their types, and pass values when executing the query.

Example:

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

When executing the query, provide variable values in a separate dictionary or object.

Example using JavaScript with Apollo Client:

const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      name
      email
    }
  }
`;

client.query({
  query: GET_USER,
  variables: { id: "123" }
}).then(response => {
  console.log(response.data);
});

5. What are fragments and how are they used?

Fragments in GraphQL allow you to share fields between multiple queries, mutations, or subscriptions, ensuring consistency across queries.

Example:

fragment userFields on User {
  id
  name
  email
}

query getUser {
  user(id: "1") {
    ...userFields
  }
}

query getUsers {
  users {
    ...userFields
  }
}

The userFields fragment is defined once and reused in both getUser and getUsers queries.

6. How do you implement authentication in an API?

Authentication in a GraphQL API often involves token-based methods like JSON Web Tokens (JWT). The process includes user login, token generation, and token validation.

Example using Python:

import jwt
from graphql import GraphQLObjectType, GraphQLField, GraphQLString, GraphQLSchema
from graphql.execution.executors.asyncio import AsyncioExecutor
from graphql_server import GraphQLServer

SECRET_KEY = 'your_secret_key'

def authenticate(token):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
        return payload['user_id']
    except jwt.ExpiredSignatureError:
        return None
    except jwt.InvalidTokenError:
        return None

def resolve_hello(obj, info):
    token = info.context['headers'].get('Authorization')
    user_id = authenticate(token)
    if user_id:
        return f'Hello, user {user_id}!'
    else:
        return 'Authentication failed.'

query_type = GraphQLObjectType(
    name='Query',
    fields={
        'hello': GraphQLField(
            GraphQLString,
            resolve=resolve_hello
        )
    }
)

schema = GraphQLSchema(query=query_type)

app = GraphQLServer(schema=schema, executor=AsyncioExecutor())

if __name__ == '__main__':
    app.run()

In this example, the authenticate function decodes and validates the JWT token.

7. Describe the difference between queries and mutations.

Queries fetch data from the server, while mutations modify data. Queries are read-only, similar to GET requests in REST, whereas mutations are write operations, akin to POST, PUT, DELETE, and PATCH requests.

8. How do you paginate results?

Pagination in GraphQL manages large data sets by breaking them into smaller chunks. Common methods include offset-based and cursor-based pagination.

Example of cursor-based pagination:

type Query {
  users(first: Int, after: String): UserConnection
}

type UserConnection {
  edges: [UserEdge]
  pageInfo: PageInfo
}

type UserEdge {
  node: User
  cursor: String
}

type PageInfo {
  endCursor: String
  hasNextPage: Boolean
}

type User {
  id: ID
  name: String
}

The users query takes first and after arguments, with UserConnection containing edges and pageInfo.

9. How do you create a custom scalar type?

Custom scalar types in GraphQL handle specific data formats not covered by default types. Define them in your schema and implement serialization, deserialization, and validation logic.

Example:

const { GraphQLScalarType, Kind } = require('graphql');

const DateScalar = new GraphQLScalarType({
  name: 'Date',
  description: 'Custom scalar type for dates',
  serialize(value) {
    return value.toISOString();
  },
  parseValue(value) {
    return new Date(value);
  },
  parseLiteral(ast) {
    if (ast.kind === Kind.STRING) {
      return new Date(ast.value);
    }
    return null;
  },
});

module.exports = DateScalar;

In your schema, define the custom scalar type and use it in type definitions.

10. Explain the concept of subscriptions.

Subscriptions in GraphQL provide real-time updates from the server to the client, establishing a long-lived connection. This requires WebSocket support.

Example using Apollo Server:

const { ApolloServer, gql, PubSub } = require('apollo-server');
const pubsub = new PubSub();

const typeDefs = gql`
  type Message {
    content: String
  }

  type Query {
    messages: [Message]
  }

  type Subscription {
    messageAdded: Message
  }
`;

const resolvers = {
  Query: {
    messages: () => []
  },
  Subscription: {
    messageAdded: {
      subscribe: () => pubsub.asyncIterator(['MESSAGE_ADDED'])
    }
  }
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  subscriptions: {
    onConnect: () => console.log('Connected to websocket')
  }
});

server.listen().then(({ url, subscriptionsUrl }) => {
  console.log(`Server ready at ${url}`);
  console.log(`Subscriptions ready at ${subscriptionsUrl}`);
});

// To trigger a subscription event
pubsub.publish('MESSAGE_ADDED', { messageAdded: { content: 'Hello, world!' } });

In this example, the messageAdded subscription allows clients to receive updates when a new message is added.

11. Describe how to implement caching in a server.

Caching in a GraphQL server can improve performance by reducing repeated data fetching. Strategies include in-memory caching, HTTP caching, persistent caching, and client-side caching.

  • In-Memory Caching: Store recent query results in memory. Libraries like dataloader can batch and cache requests.
  • HTTP Caching: Use HTTP headers to cache responses at the network level.
  • Persistent Caching: Store cached data in persistent storage like Redis or Memcached.
  • Client-Side Caching: Use libraries like Apollo Client for built-in caching mechanisms.

12. What are directives and how do you use them?

Directives in GraphQL modify query behavior. Common built-in directives are @include and @skip, which conditionally include or exclude parts of a query.

Example:

query GetUser($withEmail: Boolean!) {
  user(id: "1") {
    id
    name
    email @include(if: $withEmail)
  }
}

The email field is included only if withEmail is true.

Custom directives can add specific behavior, like marking a field as deprecated:

directive @deprecated(reason: String) on FIELD_DEFINITION

type User {
  id: ID!
  name: String!
  email: String! @deprecated(reason: "Use 'contactEmail' instead.")
  contactEmail: String!
}

13. Explain the N+1 problem and how to solve it.

The N+1 problem in GraphQL occurs when querying nested fields, leading to multiple database calls. Data loaders can batch and cache requests to reduce calls.

Example using a data loader in Node.js:

const DataLoader = require('dataloader');

const fetchPostsByUserIds = async (userIds) => {
  return userIds.map(userId => ({ userId, posts: [`Post1 for ${userId}`, `Post2 for ${userId}`] }));
};

const postLoader = new DataLoader(userIds => fetchPostsByUserIds(userIds));

const resolvers = {
  Query: {
    users: async () => {
      const users = [{ id: 1 }, { id: 2 }];
      return users;
    },
  },
  User: {
    posts: (user) => postLoader.load(user.id),
  },
};

14. How do you implement rate limiting in an API?

Rate limiting controls the number of requests an API can handle within a specific time frame. It helps maintain performance and reliability. Middleware can intercept requests and apply rate limiting logic.

Example using express-rate-limit in Node.js:

const express = require('express');
const rateLimit = require('express-rate-limit');
const { graphqlHTTP } = require('express-graphql');
const schema = require('./schema');

const app = express();

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  message: 'Too many requests from this IP, please try again later.'
});

app.use('/graphql', limiter, graphqlHTTP({
  schema: schema,
  graphiql: true,
}));

app.listen(4000, () => {
  console.log('Server is running on port 4000');
});

In this example, the express-rate-limit middleware limits each IP address to 100 requests per 15 minutes.

15. How do you handle nested queries?

Nested queries in GraphQL allow fetching related data in a single request. Define relationships in the schema and implement resolvers to fetch related data.

Example:

type Author {
  id: ID!
  name: String!
  books: [Book]
}

type Book {
  id: ID!
  title: String!
  author: Author
}

type Query {
  authors: [Author]
  books: [Book]
}

Resolvers:

const resolvers = {
  Query: {
    authors: () => getAuthors(),
    books: () => getBooks(),
  },
  Author: {
    books: (author) => getBooksByAuthorId(author.id),
  },
  Book: {
    author: (book) => getAuthorById(book.authorId),
  },
};

In this example, the Author type has a resolver for the books field, and the Book type has a resolver for the author field.

16. What is the role of the context object in resolvers?

The context object in GraphQL is an optional third argument passed to resolvers, sharing information across all resolvers in a request. It’s useful for authentication or database connections.

Example:

const { ApolloServer, gql } = require('apollo-server');

const typeDefs = gql`
  type Query {
    currentUser: User
  }

  type User {
    id: ID!
    name: String!
  }
`;

const resolvers = {
  Query: {
    currentUser: (parent, args, context) => {
      return context.user;
    },
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => {
    const user = getUserFromHeaders(req.headers);
    return { user };
  },
});

server.listen().then(({ url }) => {
  console.log(`Server ready at ${url}`);
});

In this example, the context object passes user information extracted from request headers to the resolver.

17. How do you handle real-time updates in GraphQL?

Real-time updates in GraphQL are handled using subscriptions, allowing clients to receive updates when specific events occur. This requires WebSocket support.

Example:

const { ApolloServer, gql, PubSub } = require('apollo-server');

const pubsub = new PubSub();
const MESSAGE_ADDED = 'MESSAGE_ADDED';

const typeDefs = gql`
  type Message {
    id: ID!
    content: String!
  }

  type Query {
    messages: [Message!]
  }

  type Mutation {
    addMessage(content: String!): Message
  }

  type Subscription {
    messageAdded: Message
  }
`;

const resolvers = {
  Query: {
    messages: () => messages,
  },
  Mutation: {
    addMessage: (parent, { content }) => {
      const message = { id: messages.length + 1, content };
      messages.push(message);
      pubsub.publish(MESSAGE_ADDED, { messageAdded: message });
      return message;
    },
  },
  Subscription: {
    messageAdded: {
      subscribe: () => pubsub.asyncIterator([MESSAGE_ADDED]),
    },
  },
};

const server = new ApolloServer({ typeDefs, resolvers });

server.listen().then(({ url }) => {
  console.log(`Server ready at ${url}`);
});

In this example, a subscription listens for new messages being added, and the messageAdded subscription is triggered when a new message is added.

18. Explain the difference between GraphQL and REST.

GraphQL and REST are different paradigms for building APIs. GraphQL allows clients to request specific data, using a single endpoint and supporting real-time updates. REST uses standard HTTP methods to interact with resources, with each resource accessed via a unique URL.

19. What are the benefits of using GraphQL over traditional APIs?

GraphQL offers several benefits over traditional APIs:

  • Efficient Data Fetching: Clients request exactly the data they need, reducing network data transfer.
  • Single Endpoint: Access all data through a single endpoint, simplifying API structure.
  • Strongly Typed Schema: Provides clear documentation and query validation.
  • Real-time Data with Subscriptions: Supports real-time updates.
  • Introspection: Self-documenting APIs allow clients to explore available data and operations.
  • Versionless API: No need for versioning, as clients request specific fields.

20. How do you optimize a query for performance?

Optimizing a GraphQL query for performance involves several strategies:

  • Query Batching: Combine multiple queries into a single request to reduce network round trips.
  • Avoid Over-fetching: Request only necessary fields to minimize data transfer and server load.
  • Use Persisted Queries: Store frequently used queries on the server to reduce parsing and validation overhead.
  • Implement Caching: Utilize caching mechanisms on both client and server sides.
  • Optimize Resolvers: Ensure efficient resolvers and use data loaders to batch and cache database requests.
  • Pagination: Implement pagination for large datasets to reduce data processing and transfer.
  • Use Aliases and Fragments: Aliases avoid name conflicts, and fragments reuse common fields.
Previous

10 C# REST API Interview Questions and Answers

Back to Interview
Next

10 GlobalLogic Interview Questions and Answers