The Kill Switch Pattern: Instant Feature Disabling
It’s 2am. Your phone buzzes. Payments are failing. Users are angry. The bug is somewhere in the new checkout flow you deployed yesterday.
You have two options: roll back the entire deployment (risky, slow) or fix the bug right now (risky, slow). Neither is great.
But if you had a kill switch, you’d flip it. New checkout disabled. Old checkout active. Problem contained. Fix the bug tomorrow when you’re awake.
In this article, we’ll learn how to implement kill switches so you’re ready when things go wrong.
What is a kill switch?
A kill switch is a feature flag that’s normally on but can be turned off instantly to disable a feature:
if (config.get('payments-enabled', { default: true })) {
processPayment()
} else {
showMaintenanceMessage()
}Unlike regular feature flags (which are often off by default and turned on for rollout), kill switches are:
- On by default — the feature runs normally
- Emergency controls — flipped only when something breaks
- Instant — changes take effect in seconds, not minutes
Why you need kill switches
Faster incident response
Without kill switch:
- Get paged at 2am
- Debug the issue (30+ minutes)
- Write a fix (15+ minutes)
- Get code reviewed (blocked until morning?)
- Deploy (10+ minutes)
- Pray it works
With kill switch:
- Get paged at 2am
- Flip the switch (30 seconds)
- Go back to sleep
- Fix it properly tomorrow
Reduced blast radius
If a new feature breaks, a kill switch lets you disable just that feature instead of rolling back everything:
// These are independent
if (config.get('new-checkout-enabled', { default: true })) {
// Bug is here, disable this
}
if (config.get('new-search-enabled', { default: true })) {
// This is fine, keep running
}Peace of mind
Knowing you can instantly disable any feature makes deploying less stressful. Ship faster, fix faster.
What’s the key difference between a regular feature flag and a kill switch?
Implementing kill switches
The basic pattern
Every kill switch follows the same pattern:
if (isFeatureEnabled('feature-name')) {
// Normal path (feature runs)
doFeature()
} else {
// Fallback path (feature disabled)
showFallback()
}The fallback should be:
- User-friendly (not an error page)
- Safe (no broken state)
- Informative (tell users what’s happening)
Node.js implementation with remote config
Here’s a production-ready implementation:
import { Replane } from '@replanejs/sdk'
interface Configs {
'payments-enabled': boolean
'checkout-enabled': boolean
'notifications-enabled': boolean
}
// Initialize config client
const replane = new Replane<Configs>({
defaults: {
// Kill switches default to true (enabled)
'payments-enabled': true,
'checkout-enabled': true,
'notifications-enabled': true,
},
})
await replane.connect({
sdkKey: process.env.REPLANE_SDK_KEY!,
baseUrl: 'https://cloud.replane.dev',
})
function isEnabled(feature: keyof Configs): boolean {
const enabled = replane.get(feature, { default: true })
if (!enabled) {
console.warn(`Kill switch active: ${feature} is disabled`)
}
return enabled
}Using it in your code:
interface OrderResult {
success: boolean
error?: string
message?: string
}
function processOrder(order: Order): OrderResult {
if (!isEnabled('checkout-enabled')) {
return {
success: false,
error: 'Checkout is temporarily unavailable. Please try again later.',
}
}
// Normal checkout flow
if (isEnabled('payments-enabled')) {
chargeCustomer(order)
} else {
// Payment disabled, queue for later
queuePayment(order)
return {
success: true,
message: 'Order received. Payment will be processed shortly.',
}
}
if (isEnabled('notifications-enabled')) {
sendConfirmationEmail(order)
}
return { success: true }
}Monitoring kill switch activation
When a kill switch is flipped, you want to know:
replane.subscribe('payments-enabled', (config) => {
if (config.value === false) {
// Kill switch activated!
alertTeam(`Kill switch activated: payments-enabled`)
logAudit(`Feature disabled: payments-enabled`)
}
})
replane.subscribe('checkout-enabled', (config) => {
if (config.value === false) {
alertTeam(`Kill switch activated: checkout-enabled`)
logAudit(`Feature disabled: checkout-enabled`)
}
})Design patterns for kill switches
Graceful degradation
When a feature is disabled, provide a reasonable alternative:
function getRecommendations(user) {
if (isEnabled('ml-recommendations')) {
// ML-powered recommendations
return mlEngine.recommend(user)
} else {
// Fallback: popular items
return getPopularItems()
}
}The user still gets recommendations, just less personalized ones.
Queue for later
For critical operations, queue them instead of failing:
function sendNotification(user, message) {
if (isEnabled('notifications-enabled')) {
notificationService.send(user, message)
} else {
// Queue for when service is restored
notificationQueue.add(user, message)
console.log(`Notification queued for ${user.id}`)
}
}Clear user messaging
Tell users what’s happening:
function processPayment(order) {
if (!isEnabled('payments-enabled')) {
return res.render('payment_unavailable', {
message: 'Our payment system is temporarily unavailable. ' +
'Your cart is saved, and you can complete checkout ' +
'when the issue is resolved.',
retryUrl: `/checkout/${order.id}`,
})
}
// Normal payment flow
// ...
}What should happen when a kill switch disables the payment feature?
What features need kill switches?
Not every feature needs a kill switch. Focus on:
External integrations
Third-party APIs can fail or have outages:
if (isEnabled('stripe-payments')) {
chargeViaStripe(order)
} else if (isEnabled('paypal-payments')) {
chargeViaPaypal(order)
} else {
queuePayment(order)
}New or risky features
Recent deployments and complex features:
if (isEnabled('new-checkout-flow')) {
newCheckout(order)
} else {
legacyCheckout(order)
}High-traffic paths
Features that affect many users:
if (isEnabled('homepage-recommendations')) {
showRecommendations()
} else {
showStaticContent()
}Resource-intensive operations
Features that could overload your system:
if (isEnabled('real-time-analytics')) {
computeAnalytics()
} else {
showCachedAnalytics()
}Testing kill switches
Kill switches need testing too:
Test the fallback path
describe('processOrder', () => {
it('should queue payment when payments disabled', () => {
// Arrange
mockConfig.set('payments-enabled', false)
// Act
const result = processOrder(testOrder)
// Assert
expect(result.success).toBe(true)
expect(result.message).toContain('Payment will be processed shortly')
expect(paymentQueue.contains(testOrder)).toBe(true)
})
})Test the switch itself
describe('kill switch propagation', () => {
it('should update when switch is flipped', async () => {
// Arrange
expect(isEnabled('feature-x')).toBe(true)
// Act
await config.set('feature-x', false)
// Assert (should update within seconds)
await sleep(2000)
expect(isEnabled('feature-x')).toBe(false)
})
})Simulate outages in staging
Regularly flip kill switches in staging to verify fallbacks work:
# Chaos engineering: disable random features
./scripts/chaos-test.sh --disable-random-featureTools for kill switches
Any remote configuration system can implement kill switches. Some options:
- Replane — Open-source, real-time via SSE, version history for auditing
- LaunchDarkly — Enterprise, has “flags off” feature
- Unleash — Open-source toggle service
With Replane, you can flip a switch in the dashboard and have it take effect across all servers in under a second via SSE.
Runbook for kill switch activation
When you need to flip a kill switch:
-
Confirm the issue — Is the feature actually broken, or is it something else?
-
Communicate — Alert the team: “Disabling [feature] due to [issue]”
-
Flip the switch — In your config dashboard, set
feature-enabledtofalse -
Verify — Check that the fallback is working correctly
-
Monitor — Watch error rates and user feedback
-
Document — Log what happened and why
-
Fix properly — Later, fix the root cause and re-enable
Summary
Kill switches let you instantly disable features when things go wrong:
- Default to on — features run normally
- Flip in emergencies — disable in seconds, not minutes
- Provide fallbacks — graceful degradation, not errors
Every critical feature should have a kill switch. When that 2am alert comes, you’ll be glad you invested the time.
Exercises
-
Identify kill switch candidates: Review an application you’ve worked on. List 5 features that should have kill switches.
-
Implement a kill switch: Add a kill switch to an existing feature. Include proper fallback behavior and logging.
-
Write a runbook: Create a document that describes how to activate kill switches in your system, who can do it, and what the process is.
-
Test your fallbacks: For each kill switch you have, verify that the fallback path actually works by disabling the feature in staging.
Discussion