GraphQL Overview

5 interview angles 5 min read source

GraphQL Overview

GraphQL is a query language for APIs, created by Facebook (2012, open-sourced 2015). Instead of fixed endpoints returning fixed shapes, clients send a query specifying exactly what they want — and the server returns exactly that.

For comparison with REST/gRPC/SOAP see ../00_api_protocols_comparison.md. This folder is the deep dive.

The defining example

REST — many endpoints, fixed shapes:

GET /api/users/42           → user object
GET /api/users/42/posts     → list of posts
GET /api/users/42/followers → list of followers

Three round trips. Each returns more than the screen needs (over-fetching).

GraphQL — one endpoint, client-specified shape:

query {
  user(id: 42) {
    name
    avatar
    posts(limit: 5) { title, excerpt }
    followers(limit: 10) { name, avatar }
  }
}

One round trip. Server returns exactly the requested fields:

{
  "data": {
    "user": {
      "name": "Alice",
      "avatar": "https://...",
      "posts": [{"title": "...", "excerpt": "..."}],
      "followers": [{"name": "...", "avatar": "..."}]
    }
  }
}

What GraphQL is and isn’t

GraphQL is GraphQL isn’t
a query language for APIs a database query language (despite the name)
typed schema a transport (it’s usually HTTP, but the spec is transport-agnostic)
client-driven shape always JSON (binary GraphQL exists; rare)
introspectable “REST replacement” — they solve different problems

GraphQL is HTTP+JSON+a schema+a query language. The transport is mundane (POST to /graphql). The interesting stuff is the schema and resolver model.

The three operations

Operation Purpose
query read data (idempotent, like GET)
mutation write data (POST/PUT/DELETE)
subscription stream data (WebSocket / SSE)
query GetUser {
  user(id: 42) { name email }
}

mutation CreateUser {
  createUser(input: {name: "Alice", email: "a@b.com"}) {
    id
    createdAt
  }
}

subscription OnNewMessage {
  messageAdded(roomId: "abc") { id, content, author }
}

See 03_queries_mutations_subscriptions.md.

When GraphQL fits

  • Many clients with different needs — mobile, web, partner integrations all hitting the same data with different field requirements.
  • Heterogeneous screens — one product page needs user+posts+follows; another needs just user+posts.
  • Aggregator UIs — combining multiple backend services into one response, avoiding waterfall round-trips from the client.
  • Rapid frontend iteration — adding new fields doesn’t need a backend deploy if the schema already has them.

When GraphQL doesn’t fit

  • Public-facing APIs with unknown clients — REST’s HTTP caching wins; rate limiting is easier.
  • Simple CRUD — GraphQL’s schema + resolver overhead isn’t justified.
  • Heavy file uploads/downloads — GraphQL multipart is awkward; REST is fine.
  • APIs where each “query” is its own product — Stripe, Twilio, SendGrid all stay REST.

The rule: GraphQL pays off when clients have diverse data needs against the same backend.

The architecture

┌──────────────────────┐
│ Client                │  ← sends query string
└──────────────────────┘
          ↓ HTTP POST /graphql
┌──────────────────────┐
│ GraphQL server        │
│  ┌─────────────────┐  │
│  │ Schema (SDL)    │  │  ← types, fields
│  ├─────────────────┤  │
│  │ Query parser    │  │
│  ├─────────────────┤  │
│  │ Validator       │  │  ← against schema
│  ├─────────────────┤  │
│  │ Executor        │  │  ← calls resolvers in order
│  └─────────────────┘  │
│  ┌─────────────────┐  │
│  │ Resolvers       │  │  ← per-field code
│  └─────────────────┘  │
└──────────────────────┘

┌──────────────────────┐
│ Data sources         │  ← DB, REST APIs, gRPC services
└──────────────────────┘

The schema is the contract. The resolvers are the implementation.

Resolvers — one function per field

async def resolve_user(parent, info, id):
    return await db.get_user(id)

async def resolve_posts(user, info):
    return await db.get_posts_by_author(user.id)

Each field has a resolver. The executor walks the query tree, calling resolvers as it goes. This is where N+1 happens — one query fetches 100 users, then the posts resolver runs 100 times, each hitting the DB.

The fix: DataLoader (batches and caches resolver calls within a single request). See 04_resolvers_n_plus_1_dataloader.md.

The “everything in one endpoint” question

GraphQL apps almost always use:

POST /graphql
Content-Type: application/json
{"query": "...", "variables": {...}, "operationName": "..."}

Same URL for every request. Implications:

  • HTTP caching doesn’t work (same URL).
  • Rate limiting needs to inspect the query.
  • Per-endpoint metrics don’t apply — instrument resolvers individually.

The “always 200” question

GraphQL returns HTTP 200 for “the server received and processed the query” regardless of whether business logic succeeded.

{
  "data": {"user": null},
  "errors": [
    {"message": "User not found", "path": ["user"], "extensions": {"code": "NOT_FOUND"}}
  ]
}

Partial responses are normal: some fields succeed, others fail. The errors array reports failures; data contains whatever succeeded.

Files in this folder

# Topic
02 Schema and types
03 Queries, mutations, subscriptions
04 Resolvers, N+1, DataLoader
05 Pagination and connections
06 Errors and versioning
07 Security
08 Python libraries

Common interview confusions

  • “GraphQL is a database.” — no. It’s an API query language.
  • “GraphQL replaces REST.” — they solve overlapping but different problems. Many teams use both.
  • “GraphQL eliminates over-fetching.” — under-fetching, yes. Over-fetching at the DB layer can be worse than REST if resolvers are naive.
  • “GraphQL is always faster than REST.” — single-roundtrip wins; per-query CPU often worse. Wash for simple cases; depends on workload.

Interview angle

  • “What is GraphQL?” — query language for APIs where the client specifies what fields it wants and the server returns exactly that. Single endpoint, typed schema, three operation types.
  • “Why use GraphQL instead of REST?” — clients with heterogeneous needs, aggregator UIs that combine many sources, rapid frontend iteration without backend deploys. Best when many different frontends hit the same backend.
  • “Why NOT use GraphQL?” — HTTP caching breaks (single URL), rate limiting needs query inspection, simple CRUD doesn’t justify the overhead, public APIs are easier as REST.
  • “What’s a resolver?” — function per field that fetches the value. Server walks the query tree, calling resolvers. Where N+1 problems live — fix with DataLoader.
  • “Why does GraphQL always return HTTP 200?” — distinguishes “server received your query” (200) from “query succeeded fully” (look at errors). Allows partial responses.