Feature Toggle Management

A feature toggle (flag) is a runtime switch that controls whether a feature is enabled. Lets you deploy code without releasing the feature.

Feature flags are essential for trunk-based development, gradual rollouts, A/B testing, and operational kill switches. Done well, they're a powerful tool. Done poorly, they accumulate as permanent technical debt.

Flag types

Different uses; different lifecycles.

Release flags

Wrap in-progress features. Enable for testing, gradual rollout. Remove after the feature is stable.

Lifetime: weeks to months.

Experiment flags (A/B test)

Compare variants for product experiments. Statistical analysis decides winner.

Lifetime: weeks for the experiment.

Permission flags

Premium features, customer-specific access. Persistent state per customer.

Lifetime: indefinite (driven by business).

Operational flags / kill switches

Quickly disable a problematic feature. Circuit breakers.

Lifetime: indefinite (kept for emergencies).

These are different tools. Treating them all the same causes problems.

Implementation

Boolean flag

if (featureFlag.isEnabled('new-checkout')) {
    showNewCheckout();
} else {
    showOldCheckout();
}

Simple, common.

Multivariate flag

const variant = featureFlag.variant('checkout-experiment');
if (variant === 'A') ...
else if (variant === 'B') ...

For experiments with multiple options.

Targeting

Flags evaluated based on user, group, percentage:

if (featureFlag.isEnabled('new-feature', { userId, groups: user.groups })) {...}

The flag service evaluates rules: this user, this group, this percentage.

Tools

Hosted services

Self-built

For small teams, a database table + simple service can suffice. The "build vs. buy" depends on scale and feature needs.

Open-source self-hosted

Unleash, GrowthBook, OpenFeature standardization.

The lifecycle

Each flag should have:

  1. Creation: with intent, owner, expected lifetime
  2. Active use: while the feature is gated
  3. Cleanup: remove the flag from code; the feature is permanent

The cleanup step is where most teams fail. Flags accumulate; old code paths persist; the codebase gets crufty.

Retirement discipline

Practices that prevent flag rot:

Owner per flag

Every flag has an owner. The owner is responsible for retirement.

Expected lifetime

Set when created. "Remove by end of Q3 2026." Not a hard deadline but a reminder.

Periodic flag audits

Quarterly: list all flags. Which are old? Owner explains why or removes.

Cleanup PRs

When retiring a flag, the PR removes both the flag check and the deprecated code path. The "if/else" becomes one path.

Tools that warn

Some flag services flag (heh) old flags as candidates for removal.

What flag is for vs. not

Flag is good for

Flag is misused for

Common patterns

Gradual rollout

Start with internal users, then 1%, then 10%, then 100%. Watch metrics at each step.

Kill switch

For risky features, the kill switch is a flag that disables in <1 minute. Used in incidents.

Sticky bucketing

For experiments, ensure a user always sees the same variant. Random per-call assignment ruins experiment quality.

Override per environment

Different defaults in dev, staging, production.

Targeting attribute caching

Flag evaluation can be expensive (network call). Cache results for short windows.

Anti-patterns

Flags that become "configuration"

A flag that says "feature is enabled for all customers" forever. That's not a flag; it's the feature being live. Remove the flag.

Flag explosion

Hundreds of flags; no one knows what they all do. Audit periodically.

Flag-driven branching that re-creates if/else hell

Code that's if (flag1) ... if (flag2) ... if (flag3) .... Refactor to clean abstractions.

Flag service as single point of failure

Production code depends on flag service being up. If it's down, what happens? Either fall back to a default or cache the last known values.

Common failure patterns

Further Reading