skip to content

Two services exchange a request and a response and each deploys on its own schedule: why is one compatibility direction not enough?

level: seniorimportance: should knowfreq 52%

answer

  1. count the hops, not the services
  2. each side both reads and writes
  3. one deploy, two opposite requirements
  4. request and response are separate contracts
  5. no enforceable order means both directions

basics

~20 s

Each side writes on one hop and reads on the other, so a single deployment imposes opposite requirements at once: an old callee must read new requests while a new caller must read old responses. Only both directions cover both hops.

solid answer

~50 s

An exchange is **two hops with two readers**, not one contract. Suppose the caller deploys first. On the request hop the caller is the writer and the callee, still on the previous version, is the reader — an old reader over new data, which is the **forward** direction. On the response hop the callee writes under the old version and the caller reads it with new code — a new reader over old data, which is the **backward** direction. One deployment, both directions, simultaneously. Reverse the order and the two requirements simply swap; there is no order that needs only one. Unless you can enforce which side deploys first — and with independent teams, replicas mid-deploy and rollbacks you generally cannot — the pair has to be specified as **fully compatible**. Note also that the request type and the response type are separate contracts and carry their guarantees independently.

code

pseudocode · 8 lines
pseudocode
// caller deployed first; callee still on the previous version
request  = write(schema = new)      // produced by the caller
decode(request,  schema = old)      // callee reads it  -> old reader, new data = FORWARD

response = write(schema = old)      // produced by the callee
decode(response, schema = new)      // caller reads it  -> new reader, old data = BACKWARD

// deploy the callee first instead and the two requirements swap over

go deeper

for a junior

Notice that in an exchange each service is a writer on one hop and a reader on the other. That double role is what makes the pair different from a one-way feed.

for a middle

Work the table both ways: for each deploy order, state which version writes and which reads on each hop, and name the direction that pairing requires.

for a senior

Show why the order is not available to you in practice — separate release calendars, mixed-version replicas, rollbacks mid-incident, callers weeks behind — and conclude that both directions are the requirement.

for a principal

Decide where the boundary of enforceable ordering lies in your organisation, and require the stronger guarantee on exchanges that cross it while allowing the weaker one where a single team truly controls both ends.

## Count hops, not services The instinct that causes the mistake is to think of "the contract between A and B" as one thing with one direction. It is two things. A request travels one way and a response travels back, and **each hop has its own writer and its own reader**. Because the same two processes swap roles between the hops, one deployment lands on the writing side of one hop and the reading side of the other at the same moment. That is the whole answer, and everything else is a consequence of it. ## The two hops under one deploy order Take the caller deploying first, with the callee still on the previous version: | Hop | Writer's version | Reader's version | Direction required | |---|---|---|---| | Request | new (caller) | old (callee) | **forward** — old reader, newer data | | Response | old (callee) | new (caller) | **backward** — newer reader, older data | Now deploy the callee first instead: | Hop | Writer's version | Reader's version | Direction required | |---|---|---|---| | Request | old (caller) | new (callee) | **backward** | | Response | new (callee) | old (caller) | **forward** | The requirements swap, but the count does not: **both orders require both directions**. There is no sequencing trick that reduces an independently-deployed exchange to a single direction. This is exactly the structural difference from a one-way stream, where a single hop means a single direction, and therefore an ordering choice that can substitute for the other direction. ## Why you cannot just pick the order In principle, if one side were guaranteed to deploy strictly before the other and never to roll back, you could rely on the single direction the resulting window needs. In practice that guarantee is rarely available: - The two services often belong to different teams on different release calendars. - Each service is itself a set of replicas that are mixed-version during its own deploy, so both versions of the caller can be calling both versions of the callee within the same minute. - Rollbacks invert whatever order you relied on, and they happen during incidents when nobody is checking compatibility tables. - A callee with many callers has no single order to enforce; some callers will be behind for weeks. Where the exchange spans an organisational boundary, the order is not merely hard to enforce — it is not yours to enforce. ## Two contracts, not one A further trap: the request type and the response type are distinct schemas with distinct readers, so they carry their guarantees **independently**. A change that is fully compatible on the response type says nothing whatsoever about the request type. Teams that reason about "the API version" as one number tend to check one of the two and ship the other blind, and the asymmetry shows up as a decode failure on whichever hop nobody examined. The practical discipline is to write the requirement per hop: 1. Name the type (request or response). 2. Name the reader that can be behind, and the writer that can be ahead. 3. Derive the direction from that pairing, and check the change against it. ## What this is not about Two neighbouring subjects are easy to drift into and worth keeping separate. One is explicit version negotiation, where the two sides agree which version to speak before exchanging payloads; that changes the problem rather than solving this one, and it is its own topic. The other is the catalogue of which concrete schema edits satisfy each direction — a different question from which direction you need. Here the deliverable is only the requirement: for an independently-deployed exchange, both directions, on each of the two types, separately. ## The senior signal An interviewer is listening for the moment the candidate stops treating the exchange as one contract and starts counting hops. Once that happens the rest follows mechanically, including the two observations that usually only come from experience: that a single service's own replicas create a mixed-version window without any second team involved, and that a rollback removes whatever ordering assumption was doing the work.

  • Is there any case where one direction is enough for a request and response pair?
    Only when you control the order and it cannot reverse. If one side is guaranteed to deploy first and never roll back, you need just the direction covering the window that order creates. Independent teams, mixed-version replicas and rollbacks usually remove that guarantee, which is why such pairs are normally specified as fully compatible.
  • Are the request type and the response type covered by one guarantee?
    No. They are separate contracts with separate readers, so each carries its own direction independently. A change that is safe on the response type says nothing about the request type, and treating the exchange as a single versioned thing hides exactly that asymmetry.
  • Does this reasoning change if both services are owned by the same team?
    Less than people expect. One team can sequence two deploys, but each service still rolls out across replicas, so both versions of each side are live simultaneously for the duration. The window shrinks from days to minutes; it does not disappear, and a rollback re-opens it.

saying these in an interview costs you the question

  • Treats a service as only a reader or only a writer
  • Applies one direction to both the request and the response
  • Assumes the caller can always be deployed after the callee
  • Forgets that one service's own replicas run mixed versions
  • Thinks a synchronous hop has no mixed-version window