Reviewing API, Schema & Query Compatibility

The most expensive review miss isn't a bug — it's a breaking change that ships to a hundred consumers you didn't know about.

Student edition · understand, apply, verify

Review binary, schema, and semantic compatibility across old/new readers and writers.

What you should be able to do: Identify field meaning and migration/rollback requirements, not just whether a change is optional.

A context-dependent practice guide, not a universal policy or verified personal history. Examples and numbers are illustrative. Use genuine, authorized evidence; personnel/privacy/legal decisions follow applicable organization policies and qualified review.

Concepts explained in this guide

Read these definitions first, or use the linked terms as you go. The examples and limitations are local to this guide; companion pages are optional.

Schema compatibility and semantic contracts

Backward compatibility lets new readers read old data; forward lets old readers read new data. Field identity/default rules differ by format.

Example. Milliseconds changed to seconds under an unchanged integer field is a breaking semantic change.

Boundary. Optional is not universally safe. Registry checks are configured structural rules, not business semantics; schema-ID headers are format/vendor conventions, not Kafka itself.

Expand-contract migrations

Change a schema in stages that allow old and new software to coexist: add a compatible representation, deploy readers/writers, backfill and reconcile, switch authority, then remove old fields only after the rollback horizon.

Example. Rename full_name by adding display_name first. Both app versions remain valid until backfill, dual-write consistency, and rollback checks are satisfied.

Boundary. Two application writes are not automatically atomic. Prefer one transaction/change-log authority and detect divergence. Dropping the old field too soon makes code rollback unsafe.

Ownership, accountability, and decision rights

Ownership names who drives follow-through; accountability names who answers for outcomes. Decision rights specify who chooses/approves and who is consulted.

Example. A migration has a coordinating owner, service owners, and named release authority.

Boundary. DRI/RACI/DACI are local conventions, not guarantees. Role labels do not replace authority, capacity, and follow-through.

Preconditions and postconditions

A precondition states required inputs/state before an operation. A postcondition states the result promised when those requirements hold and execution terminates normally. Together they make requirements testable.

Example. For sum_first(b,n), require integer 0≤n≤len(b) and numeric elements; promise the sum of b[0:n].

Boundary. State exceptions, mutation, numeric domains, and empty cases. A mathematical array range does not automatically validate a runtime input.

Outputs, outcomes, and measurement

Outputs are produced things; outcomes are changes for users/business/team. Useful metrics state definition, denominator, population, period, and attribution limits.

Example. 'Shipped a tool' is output. 'Median onboarding fell ten to six days in this cohort' is an outcome with sample/context caveats.

Boundary. Before/after does not prove sole causation. Avoid gaming, selection bias, hidden costs, and unlike-team comparisons.

In a large system, interfaces are contracts, and a diff that changes a contract can break producers and consumers that the author never sees. This question tests whether you review schema and API changes with the whole ecosystem in mind: who reads this field, who writes it, what happens during the window when old and new code both run. For an events-infra engineer this is bread and butter — Thrift/protobuf/Avro schemas flow through pipelines and land in tables that dozens of teams query, so a careless field change is a silent data-corruption or outage event, not a local bug.

Flow of reviewing API and schema compatibility: additive vs breaking, wire-format rules, defaults, schema evolution, deprecation path, downstream consumers, query semantics, data correctness
Read every interface change as a contract: additive vs breaking, wire-format field rules, defaults, forward/backward schema evolution, a real deprecation path, the full set of downstream consumers, query semantics, and long-term data correctness.

What this question is really testing

Whether you think in terms of rollout windows and independent deployment. The core insight is that producers and consumers are never upgraded atomically, so every change must be safe when old and new versions coexist. The tell is that you reach for “additive and backward-compatible” by default, know the concrete wire-format rules that make a change safe or breaking, and always ask “who are the consumers?” before approving.

How to answer What the interviewer is looking for

The classic wire-format catch — a protobuf change that looks harmless and silently corrupts every consumer:

// WRONG: reused tag 2 and changed a type -- old readers misparse the bytes
message AdEvent {
  string ad_id = 1;
  int32  region_id = 2;   // was: string campaign_id = 2;  BREAKING
}

// RIGHT: reserve the old tag/name, add the new field with a fresh tag
message AdEvent {
  reserved 2;                 // never recycle tag 2
  reserved "campaign_id";
  string ad_id = 1;
  int32  region_id = 3;       // additive: new tag, optional, defaulted
}

Common follow-ups

You can't find any consumers of a field. Safe to delete?

How to answer

How do you review an API versioning strategy?

How to answer

Schema unchanged, but the diff changes what a field means. Your call?

How to answer

What's specific about Avro/schema-registry compatibility?

How to answer
Evidence to collect with your approved tools

Worked example: an additive field is semantically breaking

This is an illustrative situation. The point is the reasoning and evidence, not copying the numbers or first-person story.

an additive field is semantically breakingDescribe old meaning: duration is milliseconds and readers depend on that unit.. Keep the same integer field but change units: New code emits seconds. Structural checks pass, but old consumers silently compute wrong results.. Introduce an explicit new representation: Use a new field/version with documented units, defaults, reader/writer compatibility, and migration scope.. Preserve rollback/history: Backfill/reconcile under one authority, verify consumers and archived data, and retain the old meaning through the supported rollback horizon.1Describe old meaning2Keep the same integer field but change units3Introduce an explicit new representation4Preserve rollback/history
Sequence for this example. The table explains the evidence and decision at each step.
StepEvidence, decision, and expected result
Describe old meaningduration is milliseconds and readers depend on that unit.
Keep the same integer field but change unitsNew code emits seconds. Structural checks pass, but old consumers silently compute wrong results.
Introduce an explicit new representationUse a new field/version with documented units, defaults, reader/writer compatibility, and migration scope.
Preserve rollback/historyBackfill/reconcile under one authority, verify consumers and archived data, and retain the old meaning through the supported rollback horizon.
Check your understanding: Are Protobuf field names always cosmetic?

No. Binary identity uses tags, but JSON/text mapping, reflection, APIs, and tooling can depend on names. Check every supported representation.

Run and change the example

Download guide_labs.py. Python 3.10+, standard library only. In its directory, run:

python3 guide_labs.py compatibility
python3 guide_labs.py test

The lab checks old/new reader fixtures and demonstrates a units change that passes a shape-only check.

The program tests its stated finite contracts. It is an educational model, not an implementation of the named production product or a proof of unmodeled distributed faults.

Sources and further verification
⌂