← Blog

Feature flag kill switches: what a merge can and cannot do

A feature flag kill switch is a flag whose only job is turning off one code path fast, usually the riskiest one in the system: a payment provider, an auth check, a call to a flaky third-party API. Flip it, and the code path stops running. No revert of the surrounding feature, no rollback of the release.

The catch is what “flip it” means. A dashboard tool changes a row in a database and the effect is instant. A git-native tool changes a file, and that file goes through a pull request and a deploy before the flag is actually off. Both get called a kill switch, but they behave differently exactly when it counts: during an incident.

This post covers what a merge-based kill switch gives you, what it costs against a real incident clock, and how to build one so that killing it is one line and one merge, not a redesign.

Key Takeaways

  • A feature flag kill switch is Martin Fowler’s “ops toggle”: built to cut a code path during an incident, not to ship a feature gradually.
  • A merge-based kill switch gives every kill a pull request, a git blame entry, and a CI check, at the cost of a deploy to take effect.
  • The kill switch’s floor is your build time. A ten-minute build means a ten-minute worst-case kill, no matter how fast someone notices the incident.
  • Listing the “off” variant first in a flag’s frontmatter means a fail-safe SDK resolves to off, not on, if evaluation ever breaks.
  • For a payment path that needs a sub-second kill, a merge-based flag is the wrong tool. Pair it with a circuit breaker or a dashboard toggle instead.

What a feature flag kill switch actually is

Martin Fowler’s toggle taxonomy calls this an ops toggle: a flag that hands operations control over a system’s behavior, built to be flipped fast when something is going wrong. That is a different job from a release flag, which ships code dark until launch day, or an experiment flag, which holds a split until the data answers a hypothesis. A kill switch exists for one moment: the one where a code path is actively hurting the product and someone needs it off before the postmortem starts.

The shape is usually the simplest a flag gets. Two variants, on and off, no targeting, no percentage split. In dif, the file looks like this:

---
id: checkout-stripe-v2
status: active
owner: sam@acme.com
surface: checkout
hypothesis: >
  The new Stripe integration holds completed checkouts flat against
  the legacy provider.
variants:
  - id: "off"
    weight: 0
  - id: "on"
    weight: 100
metrics:
  primary: completed_checkout
created: 2026-09-25
---

## Brief

Kill switch for the new Stripe integration. Set off to 100 and on to 0 to
cut over to the legacy provider during an incident.

The call site is one check:

const charge = dif("checkout-stripe-v2", {
  off: () => chargeWithLegacyProvider(order),
  on: () => chargeWithStripeV2(order),
});
return charge();

Killing it is editing two numbers, merging, and deploying. There is no separate kill-switch feature to configure, because a kill switch is just a flag with the off variant weighted to catch everyone.

What a merge-based kill switch can do

Putting the kill switch in git does not slow it down for no reason. It gives you three things a dashboard toggle does not.

  • Review before the kill lands. The weight change is a diff. A second engineer sees exactly what is being cut off before it ships, which catches the case where the “fix” kills the wrong flag.
  • A permanent record of who killed what, and why. git blame on the flag file answers “who turned this off and when” without anyone having to remember, and the commit message carries the reason into the history for good.
  • CI validation on every kill, not just the feature ships. dif validate checks that the weights still total 100 and the flag’s referenced surface exists, so a fat-fingered kill (an off weight of 10 instead of 100) fails the pull request instead of shipping half a fix. See the CLI reference for what it checks.

None of this needs a dashboard running somewhere else to keep in sync with the code. The .md format is the flag, and it is the incident record.

What it can’t do: beat the deploy clock

A merge-based kill switch’s fastest possible time to off is however long your deploy pipeline takes, not however fast someone can click. If your build and deploy take ten minutes, your worst-case kill is ten minutes, measured from the moment the PR merges, not from the moment someone notices the incident.

A dashboard tool changes the answer to an SDK evaluation call, and the client picks it up on the next check, often inside a second. For most flags, where the failure mode is “some users see the new thing,” that speed difference doesn’t matter. For a kill switch on a payment path during an active incident, ten minutes is not a rollback. It’s ten more minutes of the incident. The open-source LaunchDarkly alternative post covers this tradeoff for the whole flag model, and the same math applies here.

Make the kill one line, not a redesign

The build-time floor is fixed, but a lot of teams make their kill switch slower than it needs to be by burying the toggle inside logic that has to change too. If flipping the switch means editing a conditional, renaming a variant, or touching the call site, you have added a code review to an incident response.

The fix is to build the kill switch as a pure weight flip from day one. The variants (off, on) and the call site ship once, when the feature does, and stay untouched after that. Killing the feature later is only ever a frontmatter edit: two numbers, one commit, no code change. That’s what keeps the merge small enough to review in seconds, and it’s the only lever you have against the build-time floor.

Fail safe: order the off variant first

A kill switch has a second failure mode that has nothing to do with the incident it was built for: the SDK itself failing to evaluate the flag, because of a bad deploy, a missing user id, or a config that didn’t load. Every failure mode in dif resolves to the first listed variant and records nothing, so which variant you list first is not cosmetic. For a kill switch, list off first:

variants:
  - id: "off"
    weight: 0
  - id: "on"
    weight: 100

Weights control the normal split. Variant order controls what happens when there is no split to evaluate. Get the order backwards, and a kill switch built to fail safe fails on instead, at the one moment the SDK can’t be trusted to get it right.

When you need faster than a merge

A merge-based kill switch is the wrong tool for a critical path that needs a sub-second cutoff. A payment provider integration, an auth check gating every request, or anything where “wait for CI” isn’t an acceptable answer during an incident needs one of two things instead: a dashboard tool with an instant toggle, or a runtime circuit breaker that trips on its own without waiting for a human to open a pull request.

Most flags in a codebase aren’t on that path. A merge-based kill switch is the right default for the rest: the ops flags on non-critical surfaces, the “turn off this new integration if it’s misbehaving” flags, the ones where a ten-minute deploy is a completely acceptable worst case. Reserve the faster, heavier tooling for the small set of paths where it actually earns its cost.

Getting started

A kill switch built this way costs one file and one flag. With dif:

npm install -g @dif.sh/cli
dif init
dif new checkout-stripe-v2 --surface checkout

dif new drafts the file with control and variant_a at 50/50, status: draft, and your git email as owner. Rename the variants to "off" and "on" (quoted, or YAML reads them as booleans), set the weights, write the hypothesis, set status: active, and the kill switch is live once dif validate passes, the pull request merges, and the deploy finishes. The feature flags page covers the rest of the workflow, and feature flags in git covers what putting the whole flag model in your repo gets you beyond kill switches specifically.

FAQ

What is a feature flag kill switch? It’s a flag built to turn off one code path fast during an incident, not to gate a feature rollout. It usually has two variants, on and off, with no targeting or percentage split, so flipping it is a single, easily reviewed change.

How fast can you turn off a feature flag? It depends on where the flag lives. A dashboard tool applies the change in under a second. A git-native flag has to go through a pull request and a deploy, so the floor is your build time: a ten-minute pipeline means a ten-minute worst-case kill.

Is a kill switch the same thing as a rollback? Related but not identical. A rollback usually reverts an entire deploy back to a previous version. A kill switch turns off one specific code path while the rest of the deploy stays in place, which is faster to reason about and safer when only one integration is the problem.

Can a feature flag fail safely if the SDK breaks? Yes, if it’s built to. In dif, every failure mode (a missing user id, an unresolved flag, an SDK that never initialized) resolves to the first variant listed in the file and records nothing. List off first on a kill switch so a broken evaluation fails to off, not on.

Do I need a dashboard tool for kill switches, or is git enough? Git is enough for most of them. A merge-based kill switch gives you PR review, a permanent audit trail, and CI validation, at the cost of a deploy to take effect. Reserve a dashboard tool or a runtime circuit breaker for the small number of critical paths, like payments, that can’t tolerate that delay.

Should every feature flag have a kill switch? No. A kill switch is worth building for code paths where “something goes wrong and we need it off fast” is a real scenario: a new third-party integration, a rewritten checkout step, anything unproven in production. A simple release flag that just gates a feature launch doesn’t need the same treatment.

A feature flag kill switch built as a merge gives you review, history, and CI validation on every kill, and it costs you the instant toggle a dashboard has. Know your build time, list the off variant first, and keep the switch itself a pure weight flip so the merge stays small when it matters. For the handful of paths where a deploy is too slow, pair it with a circuit breaker or a dashboard tool instead of stretching the git-native model past its floor. Install the CLI and scaffold a flag to get started.