StaffSignal
Foundation — Quick Reference

API Design Patterns

REST vs gRPC vs GraphQL as organizational boundary decisions. Versioning is a deprecation commitment. Pagination strategies that scale.

API Design Patterns — Staff Interview Quick Reference

The 60-Second Version

  • REST is resource-oriented, stateless, and built on HTTP verbs. Best for external APIs and simple CRUD where broad tooling support matters.
  • gRPC uses protobuf (binary), supports streaming, and enforces strong typing. Best for internal service-to-service where performance and contracts matter. Not browser-friendly without a proxy layer.
  • GraphQL exposes a single endpoint with client-driven queries. Best when diverse frontends need different slices of the same data. Dangerous when it devolves into a query engine over your database.
  • API style is an organizational boundary decision, not a technology preference. External = REST. Internal = gRPC. GraphQL = product teams with heterogeneous frontend needs.
  • Every versioning strategy is a deprecation commitment. URL path versioning (/v1/) is simple but forces migration. Header versioning is cleaner but harder to discover.
  • Pagination choice has correctness implications: offset-based skips rows on insert, cursor-based can't jump to page N, keyset scales for large datasets.
  • Idempotency is a requirement, not a nice-to-have. POST is not idempotent. Client-generated idempotency keys are the only reliable way to prevent duplicate mutations.

What Staff Engineers Say (That Seniors Don't)

ConceptSenior ResponseStaff Response
Protocol choice"gRPC is faster than REST""REST at the org boundary for discoverability, gRPC internally for contract enforcement and performance"
Versioning"We use /v2/ for the new API""Every version is a support surface. We version at the field level and only cut a new path version for breaking contract changes"
Pagination"We use limit/offset""Offset pagination is wrong for any dataset with concurrent writes. Cursor-based for feeds, keyset for sorted tables"
Idempotency"We deduplicate on the server""Clients generate idempotency keys. The server stores them with a TTL. Without this, every retry is a potential double-write"
GraphQL adoption"GraphQL lets clients get exactly what they need""GraphQL shifts query complexity to the server. Without depth limits and query cost analysis, a single client can bring down your data layer"

The Numbers That Matter

  • Payload size: Keep REST responses under 1 MB; paginate anything larger. gRPC default max message size is 4 MB.
  • Timeout defaults: Service-to-service calls: 500ms-1s. External API gateway: 5-10s. Never leave timeouts unset.
  • Pagination limits: Default page size 20-50 items. Hard cap at 100-200. Cursor tokens should expire after 24h for consistency.
  • Idempotency key TTL: 24-48 hours covers retry windows without unbounded storage growth.
  • Rate limiting: Per-client defaults of 100-1000 req/s depending on tier. Always return 429 with Retry-After header.

Common Interview Traps

  • Choosing gRPC "because it's faster" without discussing tradeoffs. Staff answer: it's faster and you lose browser support, human-readable debugging, and broad proxy compatibility.
  • Ignoring idempotency on write endpoints. If you design a POST endpoint without an idempotency strategy, you've designed a bug.
  • Treating pagination as a UX concern. It's a correctness and scalability concern. Offset pagination breaks under concurrent writes.
  • Defaulting to GraphQL without discussing operational cost. Unbounded query depth, N+1 resolution, and cache invalidation complexity are real production risks.

API Style Decision Matrix

Rendering diagram...

Pagination Deep Dive

StrategyHow It WorksProsCons
Offset/Limit?offset=20&limit=10Simple, jump to any pageSkips/duplicates on concurrent inserts; O(N) on large offsets
Cursor-based?after=eyJ0...Stable under inserts; efficient for append logsCan't jump to page N; opaque tokens
Keyset?created_after=2024-01&id_gt=500Efficient at any depth; composable filtersRequires sortable unique column; harder to implement
Page token (Google-style)Server-issued opaque tokenServer controls; can change implementationToken expiry needs handling; stateful or encoded

Staff rule: If the dataset has concurrent writes (most production systems), offset pagination is wrong. Default to cursor-based for feeds, keyset for sorted tables.

Practice Prompt

Staff-Caliber Answer Shape
Expand
  1. REST for the external API. Third-party integrations expect REST — it's discoverable, well-documented, and proxy-friendly. Use resource-oriented design: GET /products, POST /products, GET /products/{id}.
  2. GraphQL as an optional BFF layer for first-party clients. Web and iOS apps need different data shapes from the same endpoints. A GraphQL layer over the REST backend lets each client request exactly what it needs without building per-client endpoints.
  3. gRPC for internal services. Product catalog service, search service, and pricing service communicate via gRPC for type safety and performance.
  4. Versioning: URL path versioning for the public REST API (/v1/products). Field-level deprecation for GraphQL (mark fields @deprecated). Protobuf versioning for gRPC (backward-compatible field additions).
  5. Idempotency: POST /orders requires a client-generated Idempotency-Key header. Server stores keys for 48 hours. Retries within that window return the cached response.

The Staff move: Use different API styles at different boundaries. Don't force one style everywhere.

Additional Traps

  • Exposing internal IDs in external APIs. Auto-increment IDs leak information (total count, creation order). Use UUIDs or hashids for external-facing resources.
  • Missing error contract. Returning bare HTTP status codes without structured error bodies makes client error handling impossible. Define an error schema: { "code": "INVENTORY_EXHAUSTED", "message": "...", "details": {...} }.
  • Tight coupling via shared types. Services sharing protobuf definitions is fine; services importing each other's domain models is a coupling trap. Define API contracts at the boundary, not the implementation.
  • No rate limiting on write endpoints. Every mutating endpoint needs per-client rate limiting. Without it, one misbehaving client can overwhelm your write path.

Where This Appears