Background
Sections
IntroductionRequirements & Problem AnalysisConstraints & AssumptionsEstimation TechniquesFunctional vs Non-Functional RequirementsMoSCoW PrioritizationSystem Design FundamentalsArchitecture DiagramClass DiagramComponent DiagramData Flow Diagram (DFD)ER Diagram (Entity-Relationship Diagram)High Level Design (HLD)Low Level Design (LLD)Sequence DiagramState DiagramUse Case DiagramData StorageDocument StoresFile StorageGraph DatabasesIn-Memory DatabasesKey-Value StoresNewSQLNoSQL DatabasesObject StorageSQL Databases (RDBMS)Time-Series DatabasesWide-Column StoresDatabase ConceptsACID PropertiesCAP TheoremConsistency ModelsIndexingNormalization & DenormalizationReplicationSharding & PartitioningTransactions & Isolation LevelsScalabilityAuto-Scaling & ElasticityConsensus & Leader ElectionLoad BalancingReplication & Read ReplicasSharding & PartitioningVertical vs Horizontal ScalingAvailability & ReliabilityBackup & Data DurabilityCircuit BreakerData ConsistencyDisaster RecoveryFault Tolerance & FailoverGraceful DegradationHigh AvailabilityNetworkingCDNDNSFirewalls & VPNHTTP & HTTPSLoad Balancer & Reverse ProxyTCP/IP & UDPWebSocketsCachingCache InvalidationCache Read/Write PatternsCaching LayersEviction PoliciesRedis vs MemcachedMessaging & CommunicationDead-Letter QueueIdempotencyKafka vs RabbitMQ vs SQSMessage QueuesPub/SubCompute & ServicesAPI GatewayContainers & OrchestrationMonolith vs MicroservicesServerlessService DiscoveryService MeshWeb Server & App ServerAPI DesignAPI Versioning & IdempotencyAuthentication & AuthorizationGraphQLgRPCPaginationRate Limiting & ThrottlingRESTSecurityAuthentication & AuthorizationData PrivacyEncryptionInput Validation & InjectionOAuth2 & JWTSecrets ManagementXSS & CSRFStorage & File SystemsBackup & RetentionBlock vs File vs Object StorageData Lakes & WarehousesDistributed File SystemsEphemeral StorageObservability & MonitoringDistributed TracingHealth ChecksLoggingMetricsSLI, SLO, SLADesign PatternsBulkhead PatternCircuit Breaker PatternCreational PatternsRate Limiter PatternRetry PatternStructural & Behavioral Patterns

GraphQL

7 min read

In a Nutshell

GraphQL is a query language and runtime for APIs that flips the control of what data comes back from the server to the client. Instead of many fixed REST endpoints each returning a predetermined shape, GraphQL exposes a single endpoint and a schema describing all available data as a graph. The client sends a query specifying exactly the fields it wants — no more, no less — and gets back precisely that shape in one round trip. This solves REST's over-fetching and under-fetching problems elegantly: a mobile screen that needs a user's name plus their last three order totals fetches exactly that, in one request, instead of hitting three endpoints and discarding unused fields.

2D minimalistic diagram showing a client sending a GraphQL query (a nested selection of specific fields like name, orders, total) to a single "/graphql" endpoint, and receiving a response whose shape mirrors the query exactly — only the requested fields, nested as requested — contrasted with a REST call returning a large fixed payload with unused fields greyed out

How It Actually Works

The Schema and the Graph

GraphQL APIs are defined by a strongly-typed schema that describes types, fields, and their relationships as a graph:

type User {
  id: ID!
  name: String!
  orders: [Order!]!          # a User connects to Orders
}
type Order {
  id: ID!
  total: Float!
  items: [Item!]!            # an Order connects to Items
}
type Query {
  user(id: ID!): User        # entry point
}

The client traverses this graph in a query, and the response mirrors the query's shape exactly.

Ask for Exactly What You Need

# Query: get one user's name + the totals of their last 3 orders
query {
  user(id: "42") {
    name
    orders(last: 3) {
      total
    }
  }
}
// Response — precisely the requested shape, one round trip
{ "data": { "user": { "name": "Ada",
    "orders": [ {"total": 42.0}, {"total": 19.5}, {"total": 88.0} ] } } }

No over-fetching (only name and total come back), no under-fetching (user + orders in one request).

The Three Operation Types

Operation Purpose REST Analogue
Query Read data GET
Mutation Write data (create/update/delete) POST/PUT/PATCH/DELETE
Subscription Real-time updates (server pushes) WebSockets/SSE

Resolvers: Where Data Comes From

Each field is backed by a resolver — a function that fetches that field's data. The GraphQL runtime walks the query, calling resolvers to assemble the response. This decouples the schema (what clients see) from the data sources (databases, other services, third-party APIs) behind it.

query → user resolver (DB) → orders resolver (DB) → total (field on order)
The schema can stitch together MANY backends into one graph.

GraphQL vs REST

Aspect REST GraphQL
Endpoints Many (per resource) One
Response shape Server-defined Client-defined
Over/under-fetching Common Avoided
Round trips Often multiple Usually one
Caching Easy (HTTP caching by URL) Harder (single endpoint, POST)
Learning curve Low Higher
File uploads / simple CRUD Natural Awkward
Versioning Explicit (/v2) Evolve schema, deprecate fields

The Costs and Pitfalls

GraphQL's flexibility introduces its own challenges:

  • Caching is harder — REST caches by URL; GraphQL's single POST endpoint needs application-level or normalized client caching (Apollo, Relay).
  • The N+1 problem moves server-side — naive resolvers fetch related data one-by-one; solved with DataLoader batching.
  • Query complexity/abuse — a client can request deeply nested, expensive queries; needs depth limiting, cost analysis, and timeouts.
  • Overkill for simple APIs — for a straightforward CRUD service, REST is simpler.

When to Choose GraphQL

Great fit:
  ✅ Many clients with different data needs (mobile vs web)
  ✅ Complex, nested, related data (social graphs, dashboards)
  ✅ Rapidly-evolving frontends that want flexibility
  ✅ Aggregating multiple backends behind one graph

Prefer REST when:
  ✅ Simple CRUD / resource-oriented APIs
  ✅ Heavy reliance on HTTP caching / CDNs
  ✅ File uploads, simple public APIs, low complexity

2D minimalistic diagram showing a GraphQL server as a single graph that stitches together multiple backend data sources (a users database, an orders service, and a third-party API) via resolvers, presenting them to clients as one unified schema, with a DataLoader batching component highlighted to show how it avoids the N+1 problem

Seeing It in Action

Scenario: Why a mobile team adopts GraphQL over REST for a profile screen.

The screen needs: user's name + avatar, their 3 most recent orders
(each with total + first item's thumbnail), and unread notification count.

REST approach (under-fetching / N+1):
  GET /users/42                        → name, avatar (+ 20 unused fields)
  GET /users/42/orders?limit=3         → 3 orders
  GET /orders/{id}/items  (×3)         → items per order
  GET /users/42/notifications/unread   → count
  = 6 round trips, lots of over-fetched data, slow on mobile networks.

GraphQL approach (one request, exact shape):
  query {
    user(id: "42") {
      name
      avatarUrl
      unreadNotifications
      orders(last: 3) {
        total
        items(first: 1) { thumbnailUrl }
      }
    }
  }
  = 1 round trip, only the needed fields, response shaped for the screen.

Server side: a DataLoader batches the per-order item lookups into a single
query, avoiding N+1 despite the nested shape.

Why this is transformative for the mobile team: on a high-latency mobile network, collapsing six round trips into one dramatically improves perceived performance, and fetching only the needed fields saves bandwidth and battery. Just as importantly, when the design team later adds a "loyalty points" field to the screen, the mobile client just adds loyaltyPoints to its query — no new endpoint, no backend deploy, no versioning. That client-driven flexibility, across many screens and app versions with different needs, is exactly what GraphQL is built for — at the cost of harder caching and the operational care (query limits, batching) the flexibility demands.

Interview Questions

  1. Q: What core problems with REST does GraphQL solve? Hint: Over-fetching (REST endpoints return fixed shapes with unused fields) and under-fetching / N+1 (assembling a view requires many REST calls). GraphQL lets the client specify exactly which fields and nested relationships it wants in a single query against one endpoint, returning precisely that shape in one round trip — client-controlled response shape instead of server-dictated.

  2. Q: What are the three GraphQL operation types? Hint: Query (read data, like GET), Mutation (write data — create/update/delete, like POST/PUT/DELETE), and Subscription (real-time server-pushed updates, typically over WebSockets/SSE). All operate against the single typed schema; queries and mutations are the everyday operations, subscriptions add live data.

  3. Q: What are resolvers, and why do they make GraphQL good at aggregation? Hint: A resolver is a function that fetches the data for a specific field. The runtime walks the query calling resolvers to assemble the response. Because each field can resolve from any source, one schema can stitch together many backends (databases, microservices, third-party APIs) into a single unified graph — decoupling what clients see from where the data lives.

  4. Q: Why is caching harder with GraphQL, and what about the N+1 problem? Hint: REST caches naturally by URL with HTTP caching/CDNs; GraphQL uses one endpoint (usually POST), so URL-based HTTP caching doesn't apply — you need normalized client caches (Apollo/Relay) or persisted queries. The N+1 problem moves server-side: naive resolvers fetch related items one-by-one; DataLoader batches these lookups into single queries to avoid it.

  5. Q: When would you choose REST over GraphQL? Hint: For simple CRUD/resource-oriented APIs, when you rely heavily on HTTP caching and CDNs, for file uploads, or for simple public APIs where GraphQL's complexity (query cost control, caching setup, learning curve) isn't justified. GraphQL shines with many clients needing different data, complex nested/related data, and rapidly-evolving frontends — not everywhere.

References

Dive Deeper