Steven's Knowledge

Feature Flags

Decouple deployment from release - toggles, targeting, gradual rollouts, experiments, and the platforms that run them

Feature Flags

A feature flag is a conditional in your code whose value is controlled at runtime by configuration, not by the build. Deploy code with a new feature off; flip it on for one user, then 5%, then everyone — without touching the artifact.

Why Use Them

Without flagsWith flags
Deploy = releaseDeploy is invisible; release is a config flip
Rollback = redeploy old versionRollback = flip the flag off
Risk concentrated at deploy timeRisk distributed: incremental exposure
QA blocks the entire branchHide unfinished work behind a flag, ship to main
Trunk-based development is hardIt's the easy path
A/B test = separate deploy pathsBuilt-in experiment framework

Four Kinds of Flags

TypeLifespanPurposeExample
ReleaseDays to weeksHide unfinished work; gradual rolloutenable_new_checkout
ExperimentWeeks to monthsA/B test variants and measurepricing_v2_experiment
Permission / EntitlementLong-livedPer-customer / plan capabilitiescustomer_has_sso
Ops / Kill SwitchLong-livedDisable a feature in an incidentdisable_recommendation_engine

The four have different lifecycles. Treat them differently — release flags should die; ops flags live forever.

The Players

PlatformNotes
LaunchDarklySaaS leader; rich targeting; experimentation suite
UnleashOpen-source (self-host or cloud); Apache-2 licensed
FlagsmithOpen-source SaaS + self-host; per-environment
PostHogProduct analytics + feature flags + session replay; open-source
SplitStrong on experimentation + metrics
ConfigCatSimple, dev-friendly, cheaper than LaunchDarkly
OpenFeatureVendor-neutral SDK spec; switch providers without rewriting
StatsigFree tier + strong experimentation; popular in startups
AWS AppConfig / Cloud-managedTightly integrated with cloud workloads

OpenFeature is the spec to know. It standardises the SDK API so your app code doesn't depend on a specific vendor.

Learning Path

A Flag in Code Looks Like

// Without flags
if (user.country === 'US') {
  return renderNewCheckout();
}
return renderOldCheckout();

// With flags
if (await flags.isEnabled('new_checkout', { userId: user.id, country: user.country })) {
  return renderNewCheckout();
}
return renderOldCheckout();

The condition moved from code to configuration. The new checkout can roll out to US users on a percentage, the experiment can compare conversion rates, and one ops command can disable it during an incident — without changing or redeploying the application.

Feature flags create technical debt. Every flag is a branch in your code that someone has to remember to delete. A six-month-old release flag is a smell. Track them, set expiry dates, and clean up — see Best Practices.

When Flags Aren't the Answer

  • Major architectural changes — a flag can't bridge incompatible data models.
  • Schema migrations — flags toggle behavior, not database structure.
  • One-time setup (initial onboarding, account creation) — use a different mechanism.
  • Anything where both branches incur full cost — code complexity, latency, memory.

Flags are about behavior under conditions. If both branches always run, you've just added overhead.

On this page