Managed IT · October 6, 2026 · intSignal Team

Flipping the Flag Before the Backend Is Ready

Share this article

Feature flags are usually discussed as a safety mechanism: ship dark, enable for one percent, roll back instantly. That is true when the flag controls something your own code does.

It is not true when the flag controls which contract you speak to another system. There, the flag is not a dimmer switch. It is a pair of wires, and connecting them in the wrong order takes the feature from working to completely broken.

We nearly did exactly that.

The setup

Our billing platform was migrating its product catalogue from one set of identifiers to another. The agreed sequence was explicit:

  1. The billing side brings up the new identifiers additively — old and new both resolve.
  2. They confirm that is live.
  3. We switch the website to send the new identifiers.
  4. They retire the old ones.

Our end of that was one constant:

export const CATALOG_CUTOVER_DONE = false;

export const PLAN_SLUG = CATALOG_CUTOVER_DONE
  ? { core: "core", pro: "pro", "ai-suite": "ai-suite" }
  : { core: "starter", pro: "business", "ai-suite": "enterprise" };

Flip that to true before step 2 and every checkout sends identifiers the billing system has never heard of. The lookup fails, the API returns invalid_plan, and the person trying to buy is told "please choose a plan" — on a page where they have already chosen one. Self-serve revenue goes to zero, silently, with no error on our side at all.

Why ordering mistakes happen even when everyone knows the order

Nobody forgets a plan this simple. What actually goes wrong is subtler:

  • The flag is small and the diff is large. Flipping one boolean inside a fifty-file change is invisible in review.
  • "Ready" is ambiguous. The other team has built it, in staging, behind their own flag. Somebody hears "it's done".
  • Pressure inverts the order. The display change is visible and wanted; the dependency is not. "Just ship the visible part" is a very easy sentence to say.
  • The failure is on someone else's side. Your tests pass. Your build is green. Your monitoring is fine. The breakage appears in their logs and your revenue.

Make the flag defend itself

Three cheap mechanisms, in order of value.

Pin the current state in a test

check("the catalogue cutover has not been flipped yet", CATALOG_CUTOVER_DONE === false);

This looks absurd — a test asserting a constant equals its own value. It is the most useful line in the file, because it means the flag cannot change as a side effect. Anyone flipping it must also edit the test, which puts the decision in the diff where a reviewer sees it and has to agree.

When we ran mutation testing across this module, "cutover flipped before the backend is ready" was one of the mutants. Before this test existed, it survived — the suite was entirely happy to point checkout at a catalogue that did not exist.

Write the sequence where the flag lives

Not in a ticket, not in chat. In the file:

/**
 * ROLLOUT SWITCH — which catalogue identifiers the browser sends.
 *
 * Their sequence: they bring up core/pro/ai-suite additively, signal "new tiers
 * live", then we flip. Flipping before that signal sends identifiers the backend
 * does not resolve, and every self-serve signup fails at the plan lookup.
 *
 * Set this to true — and only this — once the billing side confirms.
 */

Six months from now the ticket is closed and the chat is unsearchable. The comment is two lines above the thing someone is about to change.

Make the flag the only source of truth

One constant drove the identifiers everywhere — pricing CTAs, the signup form, deep-link parsing. If the mapping had been duplicated in three files, a partial flip becomes possible: some paths on the new identifiers, some on the old. That is worse than either state, and far harder to diagnose.

Watch for identifiers that change meaning

Ours had a genuine trap. Before the migration, our AI Suite tier mapped to the legacy identifier enterprise. After it, enterprise was to mean the sales-only tier that must never reach checkout.

The same string, two meanings, depending on which side of the cutover you are on. A mistimed deploy there does not produce an error — it produces a successful purchase of the wrong thing.

We asked for a distinct identifier rather than trying to time it. When a migration reuses a name for a different concept, rename it instead of sequencing around it. Sequencing can be got wrong; a different string cannot be confused.

When the flip is finally safe

Flip it, deploy it, and verify against the live system immediately — not the staging one:

curl -s -X POST https://api.example.com/checkout \
  -H 'Content-Type: application/json' \
  -d '{"applicationId":"<real>","planSlug":"pro"}'

A plan_not_available or invalid_plan here means the other side is not actually ready, whatever was said. Better to find that with a curl than with a customer.

Then update the pinned test to assert the new state, so the next person inherits the same protection in the new direction:

check("the catalogue cutover is flipped", CATALOG_CUTOVER_DONE === true);

That test now guards against a silent revert, which is the mirror-image bug and just as quiet.

Share this article