Operations

Feature Flags & Progressive Delivery

Intermediate

Feature flags let you ship code to production with a feature switched off, then turn it on gradually and turn it off instantly if something goes wrong. They are what make trunk-based development safe. But a flag is also a runtime branch and a piece of state, so it needs discipline: clear names, safe defaults, and removal once it has done its job.

A feature flag separates deploy (the code is in production) from release (users can see it). That lets you merge small, incomplete work to trunk safely, roll a feature out to 1% then 100%, and turn off a misbehaving feature without a redeploy. It is a core enabler of Trunk-Based Development and CI/CD.

The downside: every flag is a branch in behaviour that you must test in both states, and flags left lying around become confusing dead code and a risk. Treat them as temporary by default, and make the default the safe one.

Use flags well

Don't let flags rot

Unsafe default, never removed if (flags.Get("new-payments")) // throws if service down
useNewPayments();
// merged 8 months ago, fully on, flag + old path still here

A flag failure breaks payments instead of falling back, and long-dead code lingers behind a stale flag nobody dares delete. Both are avoidable.

Safe default, owned, scheduled // default false; missing/failed lookup => old, working path
if (flags.GetOrDefault("new-payments", false)) useNewPayments();
// JIRA-1234: remove flag + old path after 100% rollout (owner: payments)

Failing closed keeps payments working, and the flag has an owner and a removal plan so it will not become permanent debt.

Self-review checklist

Why it matters: Feature flags are what let us integrate continuously, release gradually, and recover instantly. Left unmanaged, they become a tangle of stale toggles and surprising behaviour. Safe defaults, clear ownership, and disciplined removal keep them an asset for fast, safe delivery rather than a new source of risk.