LearnThatStack Ace your next interview

GraphQL Frontend Development.
Cheat sheet.

Quick reference for GraphQL Frontend Development - sectioned for fast scanning. Skim the part you're shaky on, walk in confident.

Frontend Development 16-section reference ~7 min read

Summary

GraphQL is a query language and runtime for APIs that enables clients to request exactly the data they need. Developed by Facebook, it provides a complete description of API data through a strongly-typed schema, allowing frontend developers to fetch multiple resources in a single request. Unlike REST, GraphQL operates through a single endpoint and eliminates over-fetching and under-fetching problems. This cheatsheet covers GraphQL fundamentals, frontend integration patterns, and best practices for technical interviews.

Core Concepts

What is GraphQL?

  • Query Language for APIs and runtime for executing queries
  • Single Endpoint (typically /graphql)
  • Strongly Typed schema
  • Client specifies exact data needs

GraphQL vs REST

GraphQL REST
Single endpoint Multiple endpoints
No over/under-fetching Fixed data structure
Strongly typed No built-in type system
Single request for nested data Multiple requests (N+1 problem)

Query Basics

Simple Query

query {
  user(id: "123") {
    name
    email
  }
}

Nested Query

query {
  user(id: "123") {
    name
    posts {
      title
      comments {
        text
      }
    }
  }
}

Aliases

query {
  firstUser: user(id: "1") {
    name
  }
  secondUser: user(id: "2") {
    name
  }
}

Mutations

Basic Mutation

mutation {
  createUser(input: {
    name: "John Doe"
    email: "john@example.com"
  }) {
    id
    name
    email
  }
}

Update Mutation

mutation UpdateUser($id: ID!, $name: String!) {
  updateUser(id: $id, name: $name) {
    id
    name
  }
}

Delete Mutation

mutation {
  deleteUser(id: "123") {
    success
    message
  }
}

Subscriptions

Basic Subscription

subscription {
  messageAdded(channelId: "123") {
    id
    text
    user {
      name
    }
  }
}

WebSocket Connection

// Using Apollo Client
const subscription = client.subscribe({
  query: MESSAGE_ADDED_SUBSCRIPTION
}).subscribe({
  next: (data) => console.log(data),
  error: (err) => console.error(err),
});

Variables

Query with Variables

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

Variable Usage

// In Apollo Client
const { data } = useQuery(GET_USER, {
  variables: { userId: "123" }
});

Fragments

Basic Fragment

fragment UserInfo on User {
  id
  name
  email
}

query {
  user(id: "123") {
    ...UserInfo
  }
}

Inline Fragment

query {
  search(text: "GraphQL") {
    ... on User {
      name
      email
    }
    ... on Post {
      title
      content
    }
  }
}

Directives

@include and @skip

query GetUser($showEmail: Boolean!) {
  user(id: "123") {
    name
    email @include(if: $showEmail)
    phone @skip(if: $showEmail)
  }
}

Custom Directives

query {
  user(id: "123") {
    name
    email @deprecated(reason: "Use primaryEmail")
    primaryEmail
  }
}

Type System

Scalar Types

  • Int: Signed 32-bit integer
  • Float: Signed double-precision floating-point
  • String: UTF-8 character sequence
  • Boolean: true or false
  • ID: Unique identifier

Object Types

type User {
  id: ID!
  name: String!
  email: String
  posts: [Post!]!
}

Input Types

input CreateUserInput {
  name: String!
  email: String!
}

Enums

enum UserRole {
  ADMIN
  USER
  GUEST
}

Interfaces

interface Node {
  id: ID!
}

type User implements Node {
  id: ID!
  name: String!
}

Unions

union SearchResult = User | Post | Comment

Frontend Integration

Apollo Client Setup

import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: 'https://api.example.com/graphql',
  cache: new InMemoryCache()
});

React Hooks

useQuery

import { useQuery, gql } from '@apollo/client';

const GET_USERS = gql`
  query GetUsers {
    users {
      id
      name
    }
  }
`;

function Users() {
  const { loading, error, data } = useQuery(GET_USERS);
  
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  
  return data.users.map(user => (
    <div key={user.id}>{user.name}</div>
  ));
}

useMutation

const CREATE_USER = gql`
  mutation CreateUser($input: CreateUserInput!) {
    createUser(input: $input) {
      id
      name
    }
  }
`;

function AddUser() {
  const [createUser, { data, loading, error }] = useMutation(CREATE_USER);
  
  const handleSubmit = () => {
    createUser({
      variables: {
        input: { name: "John", email: "john@example.com" }
      }
    });
  };
  
  return <button onClick={handleSubmit}>Add User</button>;
}

useSubscription

const MESSAGE_SUBSCRIPTION = gql`
  subscription OnMessageAdded($channel: String!) {
    messageAdded(channel: $channel) {
      id
      text
    }
  }
`;

function Messages({ channel }) {
  const { data, loading } = useSubscription(MESSAGE_SUBSCRIPTION, {
    variables: { channel }
  });
  
  return <div>{data?.messageAdded?.text}</div>;
}

Error Handling

Query Error Handling

const { loading, error, data } = useQuery(GET_USER, {
  errorPolicy: 'all', // 'none' | 'ignore' | 'all'
  onError: (error) => {
    console.error('GraphQL error:', error);
  }
});

Mutation Error Handling

const [mutate] = useMutation(CREATE_USER, {
  onError: (error) => {
    if (error.graphQLErrors.length > 0) {
      // Handle GraphQL errors
    }
    if (error.networkError) {
      // Handle network errors
    }
  }
});

Performance Optimization

Query Batching

const client = new ApolloClient({
  uri: '/graphql',
  cache: new InMemoryCache(),
  batchInterval: 10, // milliseconds
  batch: true
});

Caching Strategies

const { data } = useQuery(GET_USER, {
  fetchPolicy: 'cache-first', // Default
  // Other options: 'cache-only', 'network-only', 'no-cache', 'cache-and-network'
});

Pagination

Offset-based

query GetPosts($offset: Int!, $limit: Int!) {
  posts(offset: $offset, limit: $limit) {
    id
    title
  }
}

Cursor-based

query GetPosts($after: String, $first: Int!) {
  posts(after: $after, first: $first) {
    edges {
      node {
        id
        title
      }
      cursor
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

Lazy Queries

const [getUser, { loading, data }] = useLazyQuery(GET_USER);

// Execute when needed
const handleClick = () => {
  getUser({ variables: { id: "123" } });
};

Best Practices

1. Fragment Colocation

Keep fragments near components that use them

// UserProfile.js
export const USER_FRAGMENT = gql`
  fragment UserProfile on User {
    id
    name
    avatar
  }
`;

2. Optimistic Updates

const [updateUser] = useMutation(UPDATE_USER, {
  optimisticResponse: {
    updateUser: {
      __typename: 'User',
      id: userId,
      name: newName
    }
  }
});

3. Cache Updates

const [createPost] = useMutation(CREATE_POST, {
  update(cache, { data: { createPost } }) {
    cache.modify({
      fields: {
        posts(existingPosts = []) {
          const newPostRef = cache.writeFragment({
            data: createPost,
            fragment: gql`
              fragment NewPost on Post {
                id
                title
              }
            `
          });
          return [...existingPosts, newPostRef];
        }
      }
    });
  }
});

4. Error Boundaries

import { ErrorBoundary } from 'react-error-boundary';

function GraphQLErrorFallback({ error }) {
  return <div>GraphQL Error: {error.message}</div>;
}

<ErrorBoundary FallbackComponent={GraphQLErrorFallback}>
  <App />
</ErrorBoundary>

5. Avoid N+1 Queries

Use DataLoader on backend or request nested data in single query

6. Use Fragments for Reusability

Define common field sets once and reuse

7. Implement Proper Loading States

Show skeletons or spinners during data fetching

Key Interview Concepts

The N+1 Query Problem

Definition: Performance issue where fetching a list results in 1 query for the list + N queries for each item's related data

REST Example (N+1 problem):

// 1 query for users
GET /users
// N queries for each user's posts
GET /users/1/posts
GET /users/2/posts
// ... N times

GraphQL Solution (single query):

query {
  users {
    id
    name
    posts {  # Fetched in same request
      title
    }
  }
}

Schema Evolution & Versioning

GraphQL Philosophy: Evolution over versioning

Approach Implementation
Field Deprecation @deprecated(reason: "Use newField")
Additive Changes Add new fields without breaking existing
Input Type Extension Add optional fields to inputs
Schema Introspection Clients discover capabilities dynamically

Resolver Architecture

Definition: Functions that populate data for schema fields

// Resolver structure
const resolvers = {
  Query: {
    user: (parent, args, context, info) => {
      // parent: Previous resolver result
      // args: Field arguments
      // context: Shared across resolvers (auth, DB)
      // info: Query AST and schema info
      return context.db.getUser(args.id);
    }
  },
  User: {
    posts: (parent) => {
      return getPostsByUserId(parent.id);
    }
  }
};

Authentication Strategies

const client = new ApolloClient({
  uri: '/graphql',
  headers: {
    authorization: localStorage.getItem('token') || '',
  }
});

Schema Composition Patterns

Schema Stitching: Combining multiple GraphQL schemas

// Legacy approach
const stitchedSchema = stitchSchemas({
  schemas: [userSchema, productSchema],
  mergeTypes: true
});

Federation (Modern approach):

# User service
type User @key(fields: "id") {
  id: ID!
  name: String
}

# Product service  
type Product @key(fields: "id") {
  id: ID!
  price: Float
  owner: User @provides(fields: "id")
}

GraphQL Execution Pipeline

1. Parse: Convert query string to AST
   ↓
2. Validate: Check against schema rules
   ↓
3. Execute: Run resolver functions
   ↓
4. Format: Shape response to match query

File Upload Handling

// Using Apollo Upload Client
const UPLOAD_FILE = gql`
  mutation UploadFile($file: Upload!) {
    uploadFile(file: $file) {
      url
    }
  }
`;

DataLoader Pattern

Purpose: Batch and cache database queries to solve N+1 at resolver level

const userLoader = new DataLoader(async (userIds) => {
  // Batch multiple user requests into single query
  const users = await db.getUsersByIds(userIds);
  // Return in same order as requested
  return userIds.map(id => users.find(u => u.id === id));
});

// In resolver
user: (parent, { id }) => userLoader.load(id);

Real-time Update Strategies

Strategy Use Case Pros Cons
Subscriptions Live data (chat, notifications) True real-time, efficient WebSocket complexity
Polling Periodic updates Simple implementation Network overhead
Manual Refetch User-triggered updates Full control Not real-time
Optimistic UI Instant feedback Better UX Complex rollback

Security Best Practices

// Query Depth Limiting
const depthLimit = require('graphql-depth-limit');
app.use('/graphql', depthLimit(5));

// Query Complexity Analysis
const costAnalysis = {
  maximumCost: 1000,
  scalarCost: 1,
  objectCost: 2,
  listFactor: 10
};

// Field-Level Authorization
const resolvers = {
  User: {
    email: (parent, args, context) => {
      if (context.user.id !== parent.id) {
        throw new ForbiddenError('Cannot access email');
      }
      return parent.email;
    }
  }
};

Quick Reference

Apollo Client Methods

  • useQuery() - Fetch data
  • useMutation() - Modify data
  • useSubscription() - Real-time updates
  • useLazyQuery() - Fetch on demand
  • useApolloClient() - Direct client access

Cache Methods

  • cache.writeQuery() - Write entire query
  • cache.writeFragment() - Write fragment
  • cache.modify() - Modify existing data
  • cache.evict() - Remove from cache
  • cache.gc() - Garbage collection

Fetch Policies

  • cache-first - Check cache first (default)
  • network-only - Always fetch from network
  • cache-only - Only use cache
  • no-cache - Fetch and don't cache
  • cache-and-network - Return cache, then network

Interview Preparation Checklist

  • ✅ Core Concepts: Queries, mutations, subscriptions, schemas
  • ✅ Type System: Scalars, objects, interfaces, unions, enums
  • ✅ Frontend Tools: Apollo Client, Relay, urql basics
  • ✅ Performance: N+1 problem, DataLoader, caching strategies
  • ✅ Real-time: Subscriptions, WebSocket implementation
  • ✅ Best Practices: Fragment colocation, optimistic updates
  • ✅ Security: Query depth, complexity, authorization
  • ✅ Architecture: Schema design, federation, stitching

Key Takeaway: GraphQL empowers frontend developers to request exactly the data they need in a single query, eliminating over-fetching and under-fetching while providing strong typing and excellent developer experience.

Found this useful? Pass it on.
Pro · $10/mo

The sheet is free. Pro goes deeper.

Pro opens the full question library behind every sheet, every refresher and a monthly AI allowance. One subscription, all formats.

Full question library All refreshers Cancel anytime