Q35 of 37 · API testing
How do you handle backwards-compatible API changes that require client coordination?
Short answer
Short answer: Treat the API change as one of three deploy phases: add new (non-breaking, server-only), migrate clients (rolling, both old and new contracts work), remove old (after telemetry confirms no callers). Tests cover both shapes during the transition. Telemetry on the deprecated endpoint drives the removal date.
Detail
"Backwards-compatible" still requires careful staging. The deploy contract:
Phase 1 — Add new shape, keep old working. Server-side change only. New field added to response, new endpoint added, new field accepted on input. Old clients see no difference; new clients can opt in.
Tests:
- Existing tests for the old shape still pass (backwards compat).
- New tests cover the new shape.
- Both shapes get hit in CI.
test('GET /users returns legacy fields', async () => {
const res = await request.get('/users/42');
const body = await res.json();
expect(body.first_name).toBeDefined(); // legacy
expect(body.firstName).toBeDefined(); // new
});
Phase 2 — Migrate clients.
- Update each consumer (web, mobile, partner) to use the new shape.
- Mark the old fields as deprecated in the schema and add response headers signalling deprecation.
- Track usage of the old shape via telemetry per caller (
X-Caller-Idor auth-derived).
This is where most "backwards compat" plans fail: the old shape lingers for years because nobody chases the last few callers. Tag deprecated fields and make a list of remaining callers visible.
Phase 3 — Remove old shape. Only when telemetry shows zero callers for N weeks. Communicate the change, give a final notice, then remove.
// Test that should fail until phase 3 ships
test('legacy first_name is removed', async () => {
const res = await request.get('/users/42');
const body = await res.json();
expect(body.first_name).toBeUndefined();
});
The cross-team coordination:
- Engineering: feature flag the new shape; phase the rollout.
- QA: tests for both shapes during transition; clearly tagged.
- Customer success / DevRel: communications to public API customers.
- Product: deadlines for each phase, including a hard "we will remove on date X."
Test strategy specifics:
- Both-shapes tests during phase 1-2. Unify into one test using
expect.objectContainingto cover both:
expect(body).toEqual(expect.objectContaining({
first_name: expect.any(String),
firstName: expect.any(String),
}));
- Removal tests queued (skipped) until phase 3 lands:
test.skip('legacy fields removed (queued for phase 3)', ...);
- Telemetry assertions: a test that pings the analytics API and asserts "zero callers in the last 30 days" before phase 3 unlocks.
Anti-patterns:
- Skipping phase 2 — the old shape never gets removed and the API surface area grows.
- Telling customers "deprecated, removing soon" without a date — nobody acts until the deadline.
- Tests that quietly cover only the new shape — you lose the regression on backwards compatibility before phase 2 finishes.
The senior signal: thinking in deploy phases, treating telemetry as the gate to phase 3, and explicit cross-team coordination beyond writing tests.
// WHAT INTERVIEWERS LOOK FOR
// COMMON PITFALL
// Related questions