# Feature Flags in .NET: Deploy on Tuesday, Release on Thursday

Deploying code and releasing a feature are two different events, and treating them as one is how teams end up with big-bang releases. A feature flag separates them. The code ships dark, switched off, and you turn it on for the right people at the right time. In .NET the library for this is `Microsoft.FeatureManagement`, and the basics take only a few lines.

### The basics

csharp

```csharp
builder.Services.AddFeatureManagement();
```

csharp

```csharp
if (await features.IsEnabledAsync("NewCheckout"))
{
    return await newCheckout.ProcessAsync(order);
}

return await legacyCheckout.ProcessAsync(order);
```

For MVC controllers, the `[FeatureGate("NewCheckout")]` attribute hides an entire action when the flag is off. The library also ships with built-in filters for percentage rollouts, time windows, and targeting specific users or groups, so a flag doesn’t have to be a simple on or off switch.

### Roll out in stages

The pattern I like is internal users first, then a small percentage of real traffic, then everyone, with a look at error rates and latency between each step. On payment flows I’m especially strict about this, because a mistake costs real money and the blast radius should start small. A flag turns a risky release into a series of small, reversible decisions.

### The kill switch is the real prize

The most valuable property of a flag is turning a feature off without a redeploy. That only works if the flag is dynamic. A flag baked into `appsettings.json` usually means a redeploy to change, which defeats the purpose in an emergency. Azure App Configuration with the feature management library lets you flip a feature off in seconds, with the app picking up the change on its next refresh.

### Flags are temporary by design

Every flag is a branch in your code, and every branch has to be maintained. The habit that keeps this under control is creating a removal ticket on the same day you create the flag. A flag that has sat at 100 percent for six months is dead code and a trap, because nobody knows whether the old path still works.

### Test both paths and tag your telemetry

Both branches need tests, since the flag-off path is your rollback and it must be proven to work. I also tag traces with the flag state so that when errors spike, I can see which path the failing requests took:

csharp

```csharp
Activity.Current?.SetTag("feature.NewCheckout", enabled);
```

### Watch the database

Schema changes behind a flag must work with the flag in either position. Add the new column first, release the feature, and only remove the old column once the old path is gone. If flipping the flag off breaks the old code because the schema moved on, you don’t have a rollback.

### The real takeaway

A flag is a promise that you can undo a decision quickly. Keep that promise honest: make flags dynamic, test both paths, and delete them when they’ve done their job.
