When the alert fires at 3:17am, you have three options.
You can roll back the deployment, but that takes seven minutes if everything goes well, and your last release bundled six unrelated changes you don’t want to revert.
You can hot-fix the bug, but it’s 3:17am, your fingers are imprecise, and the broken code lives in a function nobody on call has looked at in months.
Or you can flip a switch.
Kill switches are the most boring and most underrated capability in any feature flag system. They aren’t about progressive rollouts. They aren’t about A/B testing. They exist for one reason: when something is on fire, you want a very large, very obvious off-button.
Forward and reverse
Most feature-flag writing focuses on the forward direction, how to roll out new code safely. Start at 1%, watch the dashboards, ramp to 10%, then 50%, then 100%.
Kill switches are the opposite. They’re about the reverse direction. The feature is already at 100%. Something is wrong. You need to take it back to 0% in seconds, not in the 20-to-40-minute round-trip of a redeploy.
The same flag primitive powers both, but the operational mindset is completely different. Forward direction is patient and observational. Reverse direction is decisive and reflexive. When you use a flag as a kill switch, you don’t ramp down, you flip.
Anatomy of a real kill switch
A flag becomes a kill switch when it satisfies three properties:
It has a safe default. If the flag service is unreachable and the SDK has no cached value, the code path defaults to the old, known-working behavior. Not the new behavior. Not undefined behavior. The pre-feature state.
It is read on every relevant code path. A flag checked once at process start and cached for the lifetime of a long-running worker is not a kill switch, it’s a deploy gate with extra steps. Real kill switches re-evaluate per request, or at minimum on a tight refresh interval.
Its disabled state is functionally complete. Turning the flag off must leave the system in a coherent state, no orphaned rows, no half-written queues, no consumers waiting for messages that will never come. If your “off” state requires a manual cleanup script, you don’t have a kill switch. You have a partial undo.
// Safe default: if anything goes wrong, fall through to legacy
const useNewPaymentPath = await client.getFlag('new-payment-path', context, {
default: false,
});
if (useNewPaymentPath) {
return await processPaymentV2(order);
}
return await processPaymentV1(order);
The defaulting matters. When the incident itself is a flag-service outage, which does happen, your kill switch had better not depend on the same service you’re trying to bypass.
Three flavors of kill
Not every incident wants a binary off-switch. Three shapes cover most real-world scenarios.
Hard kill. The feature returns nothing, or returns an empty result. Best for non-essential surfaces, recommendation widgets, related-items modules, telemetry-heavy components, where the right move is to just stop.
Graceful degradation. The feature falls back to a previous working version. New checkout flow has a bug? Route to the old checkout flow. New search ranker is returning garbage? Use the previous ranker. Users see a less-good experience, not a broken one.
Reduced functionality. The feature stays on but does less. Personalization service overloaded? Serve cached defaults instead of computing fresh recommendations. Image pipeline timing out? Serve the original image. The product still works, just at reduced fidelity.
Most production incidents are best served by graceful degradation. Keep the experience working, accept a temporary regression in quality, and let the team fix the underlying problem under daylight conditions.
The MTTM advantage
The point of all of this is mean time to mitigate. The industry obsesses over MTTR, mean time to recovery, but the meaningful number for users is how long the broken behavior is actually visible.
A typical deploy-based mitigation looks like:
| Step | Time |
|---|---|
| Detect | 4 min |
| Diagnose well enough to know what to revert | 6 min |
| Revert + CI + deploy | 18 min |
| Total | 28 min |
A kill-switch mitigation looks like:
| Step | Time |
|---|---|
| Detect | 4 min |
| Recognize “this is the flagged feature” | 1 min |
| Flip flag | 3 sec |
| Total | ~5 min |
The 23 minutes you save isn’t just operational hygiene. It’s the difference between an internal blip and a public-facing incident with a postmortem.
Anti-patterns
A few patterns make kill switches worse than useless, they create the illusion of safety without delivering it.
The kill switch behind a deploy. A team adds a boolean in code that’s supposed to disable a feature, then ships it as true. When the incident hits, disabling it requires another deploy. This is a config constant, not a kill switch.
The chicken-and-egg flag. The kill switch’s evaluation depends on the very system it’s protecting. If you put the kill switch for your database read-replica behind a flag stored in the same database, you have a problem. Kill switches must evaluate independently of the thing they kill.
The leaky off-state. Background jobs that started while the flag was on continue running after it flips. Half-completed multi-step flows hang. Caches contain data the new code path produced. Design the off-state to be self-healing, or accept that your kill switch is really only a “stop new bleeding” switch.
The undocumented switch. A kill switch nobody knows exists is functionally the same as no kill switch. If the on-call engineer can’t find the lever, they’ll reach for a deploy.
Wire them into the runbook
A kill switch isn’t useful at 3am if the on-call engineer has to go figure out which flag to flip. The flag should be named in the alerting payload, in the runbook page, and on the dashboard for the affected service. Better still, the alert links directly to the flag’s toggle in your feature-flag dashboard.
The goal is muscle memory. Page fires, engineer reads alert, engineer clicks link, flag is off. No archaeology. No Slack threads. No judgment calls under stress.
A different mental model
Progressive rollouts are how you introduce risk into production carefully. Kill switches are how you retract risk from production instantly. Most feature-flag platforms support both, but most teams only use one of them well.
Building a strong kill-switch culture isn’t really a technical change. It’s a habit: every non-trivial new feature ships behind a flag, every flag has a documented kill behavior, and every on-call engineer knows where the lever is.
The first time you reach for that lever in the middle of an incident, and it works, you’ll never ship a major feature without one again.
ShipSilently flags evaluate locally with sub-millisecond latency and safe defaults built in, so your kill switch keeps working even when the rest of the world doesn’t. Try it free.