Use Cases

Kill Switches

Instantly disable features when things go wrong

A kill switch instantly disables a feature without deploying code. Third-party service acting up? Toggle the flag off. All users get the fallback behavior within milliseconds.

The Pattern

Service works    -> flag enabled
Service flaky    -> toggle flag off
All users        -> fallback behavior (instant)
Service stable   -> toggle flag on

No rollback. No hotfix. No 3am deployment. Just a toggle.

Implementation

1. Wrap the Feature

Every significant feature gets a flag:

const useNewSearch = await client.getBooleanValue('new-search', false, {
  targetingKey: userId
});
 
if (useNewSearch) {
  return newSearchImplementation(query);
} else {
  return oldSearchImplementation(query);
}

2. Set Default to Safe

Default should be the safe, proven behavior:

  • Default variant: false (old behavior)
  • Enabled variant: true (new behavior)

If the flag service is unreachable, users get the old behavior.

3. Enable in Production

Once the feature is ready, enable the flag:

100% -> true (new search)

4. When Things Break

You notice increased errors. Disable the flag:

100% -> false (old search)

Within milliseconds, all connected clients receive the SSE event and switch to the old behavior.

What Makes a Good Kill Switch

Fast propagation. Flipswitch uses SSE - flag changes reach clients in milliseconds, not minutes.

No dependencies. The kill switch check shouldn't depend on the broken feature. If your new search is crashing the database, don't fetch the flag from that database.

Clear fallback. The old behavior must work. If you've removed the old code, the kill switch won't help.

Default to safe. If Flipswitch is unreachable, the default value should be the safe option.

Flaky Third-Party Services

Third-party services can be unreliable: timeouts, intermittent errors, rate limiting, or complete outages. Kill switches let you gracefully handle these situations without code deploys.

Complete Bypass with Fallback

When a service is completely down, skip it entirely and use fallback behavior:

const addressValidationEnabled = await client.getBooleanValue(
  'address-validation-enabled',
  true
);
 
async function validateAddress(address: Address): Promise<ValidatedAddress> {
  if (!addressValidationEnabled) {
    // Service disabled - accept input as-is
    return { ...address, validated: false };
  }
 
  try {
    return await addressValidationService.validate(address);
  } catch (error) {
    // Service failed - fallback to accepting input
    logger.warn('Address validation failed, accepting unvalidated', { error });
    return { ...address, validated: false };
  }
}

Address validation service having issues? Disable it - orders continue with unvalidated addresses (flag for manual review later).

Optional Enrichment

Some services add value but aren't critical to core functionality:

const recommendationsEnabled = await client.getBooleanValue(
  'recommendations-enabled',
  true
);
 
async function getProductPage(productId: string): Promise<ProductPage> {
  const product = await productService.getProduct(productId);
 
  if (!recommendationsEnabled) {
    return { product, recommendations: [] };
  }
 
  try {
    const recommendations = await recommendationService.getRelated(productId);
    return { product, recommendations };
  } catch (error) {
    // Recommendations failed - product page still works
    logger.warn('Recommendations unavailable', { productId, error });
    return { product, recommendations: [] };
  }
}

Recommendation service slow or failing? Disable it - product pages load faster without recommendations.

Cached Fallback

Use stale cached data when a service is unavailable:

const liveExchangeRates = await client.getBooleanValue('live-exchange-rates', true);
 
async function getExchangeRate(from: string, to: string): Promise<number> {
  const cacheKey = `rate:${from}:${to}`;
 
  if (!liveExchangeRates) {
    // Use cached rates only
    const cached = await cache.get(cacheKey);
    if (cached) return cached.rate;
    throw new Error('No cached rate available');
  }
 
  try {
    const rate = await exchangeRateService.getRate(from, to);
    await cache.set(cacheKey, { rate, timestamp: Date.now() });
    return rate;
  } catch (error) {
    // Service failed - try cache
    const cached = await cache.get(cacheKey);
    if (cached) {
      logger.warn('Using stale exchange rate', { from, to, age: Date.now() - cached.timestamp });
      return cached.rate;
    }
    throw error;
  }
}

Exchange rate API having issues? Switch to cached rates - transactions continue with slightly stale pricing.

Combining with Monitoring

Set up alerts that trigger kill switch consideration:

Error rate > 1% for 5 minutes -> Alert + consider kill switch
P99 latency > 2s for 10 minutes -> Alert + consider kill switch
Support tickets spike -> Manual review + consider kill switch

Some teams automate this:

// Automated kill switch (be careful with this)
if (errorRate > threshold) {
  await flipswitch.admin.disableFlag('new-feature');
  alertOncall('Kill switch activated for new-feature');
}

Graceful Degradation

Kill switches work best for features that can be cleanly disabled. For tightly coupled features, consider graceful degradation:

const featureLevel = await client.getStringValue('search-features', 'basic');
 
switch (featureLevel) {
  case 'full':
    return fullSearch(query);      // AI + filters + suggestions
  case 'standard':
    return standardSearch(query);  // Filters + suggestions
  case 'basic':
    return basicSearch(query);     // Just search
}

Degrade from full to standard to basic as problems escalate.

On this page