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);
}
MutableContext context = new MutableContext(userId);
boolean useNewSearch = client.getBooleanValue("new-search", false, context);

if (useNewSearch) {
    return newSearchImplementation(query);
} else {
    return oldSearchImplementation(query);
}
context = EvaluationContext(targeting_key=user_id)
use_new_search = client.get_boolean_value("new-search", False, context)

if use_new_search:
    return new_search_implementation(query)
else:
    return old_search_implementation(query)
evalCtx := openfeature.NewEvaluationContext(userID, nil)
useNewSearch, _ := client.BooleanValue(ctx, "new-search", false, evalCtx)

if useNewSearch {
    return newSearchImplementation(query)
}
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 };
  }
}
boolean addressValidationEnabled = client.getBooleanValue(
    "address-validation-enabled",
    true
);

public ValidatedAddress validateAddress(Address address) {
    if (!addressValidationEnabled) {
        // Service disabled - accept input as-is
        return new ValidatedAddress(address, false);
    }

    try {
        return addressValidationService.validate(address);
    } catch (Exception e) {
        // Service failed - fallback to accepting input
        logger.warn("Address validation failed, accepting unvalidated", e);
        return new ValidatedAddress(address, false);
    }
}
address_validation_enabled = client.get_boolean_value(
    "address-validation-enabled",
    True
)

async def validate_address(address: Address) -> ValidatedAddress:
    if not address_validation_enabled:
        # Service disabled - accept input as-is
        return ValidatedAddress(address=address, validated=False)

    try:
        return await address_validation_service.validate(address)
    except Exception as e:
        # Service failed - fallback to accepting input
        logger.warning("Address validation failed, accepting unvalidated", exc_info=e)
        return ValidatedAddress(address=address, validated=False)
addressValidationEnabled, _ := client.BooleanValue(
    ctx,
    "address-validation-enabled",
    true,
    openfeature.EvaluationContext{},
)

func validateAddress(address Address) (ValidatedAddress, error) {
    if !addressValidationEnabled {
        // Service disabled - accept input as-is
        return ValidatedAddress{Address: address, Validated: false}, nil
    }

    result, err := addressValidationService.Validate(address)
    if err != nil {
        // Service failed - fallback to accepting input
        log.Printf("Address validation failed, accepting unvalidated: %v", err)
        return ValidatedAddress{Address: address, Validated: false}, nil
    }
    return result, nil
}

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: [] };
  }
}
boolean recommendationsEnabled = client.getBooleanValue(
    "recommendations-enabled",
    true
);

public ProductPage getProductPage(String productId) {
    Product product = productService.getProduct(productId);

    if (!recommendationsEnabled) {
        return new ProductPage(product, List.of());
    }

    try {
        List<Product> recommendations = recommendationService.getRelated(productId);
        return new ProductPage(product, recommendations);
    } catch (Exception e) {
        // Recommendations failed - product page still works
        logger.warn("Recommendations unavailable for {}", productId, e);
        return new ProductPage(product, List.of());
    }
}
recommendations_enabled = client.get_boolean_value(
    "recommendations-enabled",
    True
)

async def get_product_page(product_id: str) -> ProductPage:
    product = await product_service.get_product(product_id)

    if not recommendations_enabled:
        return ProductPage(product=product, recommendations=[])

    try:
        recommendations = await recommendation_service.get_related(product_id)
        return ProductPage(product=product, recommendations=recommendations)
    except Exception as e:
        # Recommendations failed - product page still works
        logger.warning("Recommendations unavailable for %s", product_id, exc_info=e)
        return ProductPage(product=product, recommendations=[])
recommendationsEnabled, _ := client.BooleanValue(
    ctx,
    "recommendations-enabled",
    true,
    openfeature.EvaluationContext{},
)

func getProductPage(productID string) (ProductPage, error) {
    product, err := productService.GetProduct(productID)
    if err != nil {
        return ProductPage{}, err
    }

    if !recommendationsEnabled {
        return ProductPage{Product: product, Recommendations: nil}, nil
    }

    recommendations, err := recommendationService.GetRelated(productID)
    if err != nil {
        // Recommendations failed - product page still works
        log.Printf("Recommendations unavailable for %s: %v", productID, err)
        return ProductPage{Product: product, Recommendations: nil}, nil
    }
    return ProductPage{Product: product, Recommendations: recommendations}, nil
}

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;
  }
}
boolean liveExchangeRates = client.getBooleanValue("live-exchange-rates", true);

public BigDecimal getExchangeRate(String from, String to) {
    String cacheKey = "rate:" + from + ":" + to;

    if (!liveExchangeRates) {
        // Use cached rates only
        CachedRate cached = cache.get(cacheKey);
        if (cached != null) return cached.getRate();
        throw new RuntimeException("No cached rate available");
    }

    try {
        BigDecimal rate = exchangeRateService.getRate(from, to);
        cache.set(cacheKey, new CachedRate(rate, Instant.now()));
        return rate;
    } catch (Exception e) {
        // Service failed - try cache
        CachedRate cached = cache.get(cacheKey);
        if (cached != null) {
            logger.warn("Using stale exchange rate for {}/{}", from, to);
            return cached.getRate();
        }
        throw e;
    }
}
live_exchange_rates = client.get_boolean_value("live-exchange-rates", True)

async def get_exchange_rate(from_currency: str, to_currency: str) -> Decimal:
    cache_key = f"rate:{from_currency}:{to_currency}"

    if not live_exchange_rates:
        # Use cached rates only
        cached = await cache.get(cache_key)
        if cached:
            return cached["rate"]
        raise ValueError("No cached rate available")

    try:
        rate = await exchange_rate_service.get_rate(from_currency, to_currency)
        await cache.set(cache_key, {"rate": rate, "timestamp": time.time()})
        return rate
    except Exception as e:
        # Service failed - try cache
        cached = await cache.get(cache_key)
        if cached:
            logger.warning("Using stale exchange rate for %s/%s", from_currency, to_currency)
            return cached["rate"]
        raise
liveExchangeRates, _ := client.BooleanValue(
    ctx,
    "live-exchange-rates",
    true,
    openfeature.EvaluationContext{},
)

func getExchangeRate(from, to string) (float64, error) {
    cacheKey := fmt.Sprintf("rate:%s:%s", from, to)

    if !liveExchangeRates {
        // Use cached rates only
        if cached, ok := cache.Get(cacheKey); ok {
            return cached.Rate, nil
        }
        return 0, errors.New("no cached rate available")
    }

    rate, err := exchangeRateService.GetRate(from, to)
    if err == nil {
        cache.Set(cacheKey, CachedRate{Rate: rate, Timestamp: time.Now()})
        return rate, nil
    }

    // Service failed - try cache
    if cached, ok := cache.Get(cacheKey); ok {
        log.Printf("Using stale exchange rate for %s/%s", from, to)
        return cached.Rate, nil
    }
    return 0, err
}

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
}
String featureLevel = client.getStringValue("search-features", "basic");

switch (featureLevel) {
    case "full":
        return fullSearch(query);      // AI + filters + suggestions
    case "standard":
        return standardSearch(query);  // Filters + suggestions
    default:
        return basicSearch(query);     // Just search
}
feature_level = client.get_string_value("search-features", "basic")

if feature_level == "full":
    return full_search(query)      # AI + filters + suggestions
elif feature_level == "standard":
    return standard_search(query)  # Filters + suggestions
else:
    return basic_search(query)     # Just search
featureLevel, _ := client.StringValue(ctx, "search-features", "basic", openfeature.EvaluationContext{})

switch featureLevel {
case "full":
    return fullSearch(query)      // AI + filters + suggestions
case "standard":
    return standardSearch(query)  // Filters + suggestions
default:
    return basicSearch(query)     // Just search
}

Degrade from full to standard to basic as problems escalate.

On this page