9. API Design & Compatibility
Evolve contracts safely: versioning, pagination, ETag caching, and consistent error envelopes.
Question: How should a REST API handle versioning and ensure backward compatibility?
Answer: The most robust approach is URI versioning (e.g., /v1/users
). For breaking changes, a new version (/v2
) is introduced, and the old version is maintained for a deprecation period. Non-breaking changes, like adding a new JSON field to a response, can be added to the existing version.
Explanation: Contract-driven development using OpenAPI or Protobuf as the source of truth is essential. This contract should be version-controlled and used to generate server stubs and client libraries, ensuring that all parties adhere to the same schema. Enforcing backward compatibility with CI checks (e.g., buf breaking
) can prevent accidental breaking changes from being deployed.
Question: Why is cursor-based pagination generally preferred over offset-based pagination?
Answer: Cursor-based pagination is preferred for large datasets because it is more performant and stable. It avoids the performance degradation of large OFFSET
values in SQL databases, which still have to scan through all the offset rows. It is also stable in the face of new data being added, whereas offset-based pagination can skip or show duplicate items if the dataset changes between requests.
Explanation: A cursor is an opaque pointer to a specific item in the dataset (often a timestamp or a unique, sequential column value). The client requests the next page by sending the cursor from the last item it received. The server can then efficiently fetch the next N
items directly after that cursor using a WHERE
clause (e.g., WHERE created_at > 'cursor_value'
), which is much faster than OFFSET
.
Question: How do you implement conditional requests and caching (ETag/If-None-Match)?
Answer: Generate strong or weak ETags for resources and honor If-None-Match
to return 304 when unchanged. Support If-Modified-Since
where appropriate.
Explanation: Conditional requests reduce bandwidth and load; ensure cache keys include relevant query params and auth scopes.
Question: How do you roll out breaking API changes safely?
Answer: Introduce a new version, run dual-write/dual-read if needed, provide migration guides, and deprecate with timelines and telemetry to gauge client adoption.
Explanation: Versioned evolution avoids contract surprises and enables controlled cutover.
Question: PATCH vs PUT — what are the semantics?
Answer: PUT
replaces the entire resource (idempotent), PATCH
applies partial updates (may be non-idempotent unless carefully designed, e.g., JSON Patch).
Explanation: Document behavior clearly in the contract and validate payload shapes.
Question: What is a good error envelope?
Answer: Use a consistent structure (code, message, details, correlation ID) and map internal errors to stable API error codes. Consider RFC 7807 Problem Details.
Explanation: Stable contracts aid clients and observability.