Five clients each ship a version of the API baked into their app — v1 through v5 — and keep calling whatever server is live, exactly like phones that never get updated. Every step between versions changes the schema in one of two ways: an additive change only adds an optional field, so old and new payloads still parse on both ends; a breaking change renames, removes or retypes a field, so a client built before it and a server built after it can no longer agree on the shape of the data.
compatible(client, server) =
no breaking step lies strictly between
client.version and server.version
OR shimActive
- Shim toggle — with the compatibility-shim layer on, every breaking step between a client's version and the server's version is translated by an adapter, at the cost of a small extra hop; with it off, crossing even one breaking step fails the request outright.
- Server schema version — drag the server forward through its version history; clients already stuck on old versions fall further behind and cross more breaking steps.
- Step type buttons — click a version boundary to flip it between additive and breaking and watch which clients start failing (or get rescued by the shim) in real time.
- Client request rate — how often each client fires a new request; only cosmetic pacing, it doesn't change who succeeds.
Real-world relevance: this is exactly why REST/gRPC APIs version their schemas and why backends run adapter/shim layers — a mobile client on an old build can sit in the field for years, and the server has to keep serving it without ever rewriting the app.