Skip to main content
Guides Skills and frameworks API Design Interview Guide — REST vs GraphQL vs gRPC, Versioning, and Pagination
Skills and frameworks

API Design Interview Guide — REST vs GraphQL vs gRPC, Versioning, and Pagination

9 min read · April 25, 2026

A practical API design interview guide covering REST, GraphQL, gRPC, versioning, pagination, idempotency, errors, auth, rate limits, and the tradeoffs interviewers expect.

API Design Interview Guide — REST vs GraphQL vs gRPC, Versioning, and Pagination

This API design interview guide covers the decisions interviewers care about: REST vs GraphQL vs gRPC, versioning, pagination, idempotency, errors, authentication, rate limits, consistency, and backward compatibility. Strong API answers start with consumers and use cases, not with a favorite protocol. The right design for a public mobile API can be wrong for internal service-to-service traffic, bulk data export, or low-latency streaming.

API design interview guide: start with the contract

An API is a contract between producers and consumers. In an interview, clarify:

  • Who uses the API: browser, mobile app, partner, internal service, data pipeline, admin tool?
  • What are the main workflows: create, read, update, search, stream, export, sync?
  • What are the scale and latency expectations?
  • Is the API public, private, or partner-only?
  • What are the security and permission boundaries?
  • How often will the domain model change?

A strong opening: “I would design the API around stable resources and client workflows, then choose REST, GraphQL, or gRPC based on access patterns, coupling, and performance. I would make pagination, errors, idempotency, and versioning explicit from day one because those are expensive to retrofit.”

That framing tells the interviewer you know API design is product design plus distributed systems, not endpoint naming.

REST: resource-oriented and broadly understandable

REST-style APIs model resources and use HTTP semantics. They are a good default for public APIs, CRUD-heavy products, and systems where caching, observability, and broad client compatibility matter.

Example resources for a job platform:

GET /jobs?location=remote&level=senior
GET /jobs/{job_id}
POST /applications
GET /applications/{application_id}
PATCH /applications/{application_id}

Use nouns for resources, not verbs for every action. Prefer POST /applications over POST /applyToJob when the action creates an application resource. But do not be dogmatic: some domain actions, such as POST /invoices/{id}/void, can be clearer than pretending everything is a field update.

HTTP methods matter:

| Method | Use | Idempotent? | |---|---|---| | GET | Read | Yes | | POST | Create or action | Usually no | | PUT | Replace | Yes | | PATCH | Partial update | Usually yes if designed carefully | | DELETE | Delete | Yes in effect |

REST strengths: simple mental model, caching support, standard status codes, easy tooling, browser compatibility, and durable public contracts. REST weaknesses: over-fetching, under-fetching, many round trips for nested views, and awkward modeling for graph-shaped data.

GraphQL: client-shaped data with schema discipline

GraphQL lets clients request exactly the fields they need from a typed schema. It is useful when clients have varied data needs, when screens combine many resources, or when frontend teams need flexibility without many bespoke endpoints.

Example:

query JobPage($id: ID!) {
  job(id: $id) {
    title
    company { name logoUrl }
    salaryRange { min max currency }
    similarJobs(limit: 5) { id title }
  }
}

GraphQL strengths:

  • clients avoid over-fetching and under-fetching
  • schema is introspectable and strongly typed
  • frontend teams can iterate without new endpoint for every screen
  • nested data can be fetched in one request

GraphQL risks:

  • query complexity and expensive nested requests
  • caching is less straightforward than resource URLs
  • authorization must be enforced at field and resolver levels
  • N+1 resolver bugs can be severe
  • schema evolution requires governance

In interviews, avoid saying GraphQL is “better REST.” Say it trades server-side endpoint simplicity for schema and query execution complexity. It works best when you invest in query cost limits, persisted queries, dataloaders, observability, and schema review.

gRPC: typed service calls for internal performance

gRPC uses protocol buffers and HTTP/2 to provide strongly typed service methods, efficient serialization, streaming, and good code generation. It is common for internal microservices, low-latency systems, and polyglot backend environments.

Example service shape:

service JobService {
  rpc GetJob(GetJobRequest) returns (Job);
  rpc SearchJobs(SearchJobsRequest) returns (SearchJobsResponse);
  rpc StreamApplicationEvents(ApplicationEventRequest) returns (stream ApplicationEvent);
}

gRPC strengths: compact payloads, strict schemas, generated clients, bidirectional streaming, deadlines, and internal consistency. Weaknesses: less browser-native, harder for public developer ecosystems, less human-readable over the wire, and more operational complexity for some teams.

Decision rule: use REST for broad public APIs, GraphQL for client-driven product surfaces with varied data shapes, and gRPC for internal service-to-service APIs where strong contracts and performance matter. Many companies use all three in different layers.

Versioning and backward compatibility

Versioning is really about change management. The best API version is the one consumers do not notice because additive changes are backward compatible.

Safe changes:

  • adding optional response fields
  • adding optional request fields
  • adding new endpoints or resources
  • adding enum values if clients handle unknowns
  • relaxing validation rules carefully

Breaking changes:

  • removing or renaming fields
  • changing field types or meanings
  • making optional fields required
  • changing pagination behavior
  • changing error formats
  • changing authorization assumptions

REST versioning options include path versions (/v1/jobs), header versions, or date-based versions. Path versions are explicit and easy for public APIs. Header versions are cleaner but less visible. Date-based versions can work for large public APIs with frequent incremental changes.

GraphQL often uses schema evolution rather than global versions: add fields, deprecate fields, and monitor usage before removal. gRPC uses protobuf rules: reserve field numbers, add fields safely, and avoid reusing tags.

A senior answer: “I would optimize for backward-compatible additive changes, publish deprecation windows, track consumer usage, and only introduce a new major version when semantics truly break.”

Pagination: offset, cursor, and keyset

Pagination is a favorite interview topic because naive choices fail at scale.

| Type | How it works | Best for | Risk | |---|---|---|---| | Offset | ?limit=20&offset=40 | Small stable lists, admin tools | Slow and inconsistent on changing data | | Page number | ?page=3 | Human navigation | Same issues as offset | | Cursor | ?cursor=abc&limit=20 | Feeds, search, APIs at scale | More complex implementation | | Keyset | WHERE created_at < last_seen | Ordered large datasets | Requires stable sort keys |

Cursor pagination is often the best default for large or changing lists. The cursor should encode the position in a stable sort, not expose fragile database internals. Include next_cursor, possibly prev_cursor, and a has_more flag.

Example response:

{
  "data": [],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0Ijoi...",
    "has_more": true
  }
}

Use deterministic ordering. If sorting by created_at, include a tie-breaker like id. Without stable ordering, clients see duplicates or skipped records when new items arrive.

Errors, idempotency, and retries

Good APIs make failure understandable. Use consistent error shapes:

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Try again later.",
    "request_id": "req_123",
    "details": {}
  }
}

Use HTTP status codes meaningfully: 400 for invalid requests, 401 for unauthenticated, 403 for unauthorized, 404 for missing or hidden resources, 409 for conflicts, 422 for semantic validation, 429 for rate limits, and 5xx for server failures.

Idempotency matters for retries. If a client submits a payment, application, order, or message and times out, retrying should not create duplicates. Use idempotency keys for non-idempotent operations:

POST /applications
Idempotency-Key: 9f1b...

The server stores the key, request fingerprint, result, and expiration. If the same key is retried, return the original result. If the same key is reused with a different payload, return a conflict.

Authentication, authorization, and rate limits

Authentication answers “who are you?” Authorization answers “what are you allowed to do?” Do not blur them.

Common patterns:

  • session cookies for browser apps
  • OAuth2/OIDC for delegated access and identity
  • API keys for server-to-server or partner access
  • mTLS or service identity for internal systems
  • scoped tokens for least privilege

Authorization should be enforced server-side at the resource and action level. In GraphQL, enforce field-level and resolver-level permissions. In REST, do not rely on hidden UI controls. In gRPC, use interceptors or service-level checks plus domain authorization.

Rate limits protect reliability and fairness. Design limits by identity, token, IP, tenant, endpoint, or cost unit. Return 429 with retry information. For GraphQL, rate limiting by request count is insufficient; use query complexity or persisted queries.

Consistency, concurrency, and partial updates

APIs often sit over mutable data. Interviewers may ask how to avoid lost updates. Options include optimistic concurrency with ETags or version numbers.

Example:

GET /profiles/123 -> ETag: "v7"
PATCH /profiles/123
If-Match: "v7"

If the profile changed since the client fetched it, return 409 Conflict or 412 Precondition Failed. This prevents one client from silently overwriting another's changes.

For PATCH semantics, be clear whether omitted fields mean “unchanged” or “set to null.” Ambiguity causes bugs. Consider JSON Merge Patch or JSON Patch if the domain needs standard partial update semantics.

For long-running operations, do not hold HTTP requests open forever. Return a job resource:

POST /exports -> 202 Accepted
GET /exports/{id}

This makes progress, retry, cancellation, and failure states explicit.

Common API design interview traps

The biggest trap is choosing REST, GraphQL, or gRPC before understanding consumers. Another is designing endpoints for the database schema rather than user workflows.

Other traps:

  • no pagination or offset pagination for a fast-changing feed
  • inconsistent error formats
  • no idempotency for create operations that clients retry
  • breaking changes without deprecation tracking
  • leaking internal IDs or implementation details unnecessarily
  • treating authentication as authorization
  • ignoring rate limits and abuse
  • returning huge nested payloads without limits
  • not including request IDs for debugging
  • vague update semantics for PATCH

When debugging an API incident, mention logs with request IDs, structured errors, metrics by endpoint, latency percentiles, rate-limit counters, and consumer impact. Observability is part of API design.

Example interview answer: applications API

Prompt: “Design an API for candidates applying to jobs.”

A strong answer:

“I would expose REST resources because the workflow is resource-oriented and public-client friendly: jobs, candidates, applications, resumes, and application events. POST /applications creates an application with an idempotency key so mobile retries do not duplicate submissions. GET /applications?candidate_id=...&cursor=... uses cursor pagination with stable ordering. Errors use a consistent shape with request IDs. Authorization ensures candidates can see only their own applications and employers can see applications for their jobs. For status changes, I would use explicit transitions such as POST /applications/{id}/withdraw if the action has business rules. I would version with /v1 for public clients, prefer additive changes, and publish deprecations.”

That answer covers resources, retries, pagination, permissions, and evolution.

Resume and interview language

Strong resume bullets show contract quality and operational impact:

  • “Designed cursor-paginated REST APIs for high-volume application feeds, eliminating duplicate records during concurrent inserts.”
  • “Added idempotency keys and structured error responses to payment-adjacent create endpoints, reducing duplicate submissions after client retries.”
  • “Introduced GraphQL persisted queries and complexity limits for mobile clients, cutting over-fetching while protecting backend services.”
  • “Migrated internal service calls to gRPC with protobuf contracts and deadlines, reducing p95 latency by 28%.”

Avoid “built APIs” by itself. Name the design problem and the reliability or consumer outcome.

Prep checklist

Before an API design interview, prepare decision rules for REST, GraphQL, and gRPC. Be able to design pagination, errors, idempotency, auth, rate limits, versioning, and long-running operations. Know how to make backward-compatible changes. Practice explaining cursor pagination and why stable ordering matters. The best API answers are boring in the right ways: predictable contracts, explicit failure modes, safe retries, and change management that respects consumers.