Feature Flags, Caching, and the Fine Art of Not Hammering Your API

Feature flags need to be fast. You don't want an HTTP request every time your application asks: is this thing turned on?. Let's look at what actually happens under the hood.

Microsoft.FeatureManagement: Let IConfiguration Do Its Thing

If you're using Microsoft.FeatureManagement, your feature definitions typically come from IConfiguration. A common setup looks something like:

{
  "FeatureManagement": {
    "NewFeature": true
  }
}

The important bit is that IConfiguration isn't querying appsettings.json from disk every time you ask for a value. Configuration providers load their data into the configuration system. .NET watches for changes and reloads the configuration when the file changes. The configuration root exposes change notifications, and options infrastructure can react to those changes. So you've got:

appsettings.json
       ↓
Configuration Provider
       ↓
IConfiguration
       ↓
Microsoft.FeatureManagement
       ↓
Your application

No disk read per feature check. No network request. Maybe you add your own cache just to be safe, maybe not. Either way, nice and boring. And boring is good.

But What About Remote Feature Flags?

That's where things get more interesting. FeatureFlags.app gets feature definitions over HTTP, so we don't want every call to IsEnabled() to become:

Application → HTTP → FeatureFlags.app → HTTP response

Repeated thousands of times per second. That's why the client fetches all feature definitions at startup, keeps them in memory, and refreshes them in the background every 15 minutes by default (FeatureFlags:CacheExpirationInMinutes). Flag checks read that copy and never wait on HTTP. ClearCache() asks for a refresh now. So the flow looks more like:

Background refresh (startup, then every interval)
Background service → HTTP → FeatureFlags.app → In-memory copy

Every flag check
Application → In-memory copy → Feature definitions

That's considerably less exciting for your network stack. Which is the point.

The Trade-Off: Freshness vs. Performance

Caching is one thing developers love to argue about. With a 15-minute refresh interval, a change in FeatureFlags.app can take that long to reach each instance. You're intentionally trading some freshness for better performance. Shorten the interval if you need faster updates, or call ClearCache(). If a refresh fails, the client keeps the last copy it fetched. If the app starts while FeatureFlags.app is unreachable, flags stay off until the first successful fetch.

How Other Services Do It

Some services use server-side SDKs to maintain the feature flag state and update it through streaming. Flag evaluations happen locally against the cached rules rather than making a network request for every evaluation. That gives you:

Vendor
     ↓ streaming updates
Local SDK cache
     ↓
Flag evaluation

Very fast, very resilient, and capable of continuing using the last known state if the service becomes temporarily unreachable. It's a great architecture. It's also a considerably more sophisticated - and costly - one.

Different Tools, Same Goal

Microsoft.FeatureManagement relies on .NET's configuration infrastructure. FeatureFlags.app refreshes an in-memory copy on a configurable interval. Others maintain locally cached rulesets and push updates. Different implementations. Same fundamental goal.

Don't make your feature flag evaluation depend on a network round trip. Your application has enough exciting problems already.