Skip to main content
Back to blogs
CI/CD

Feature Flags and the Case for Progressive Delivery

How feature flags decouple deployment from release, reduce blast radius, and why every team shipping to production should use them.

March 18, 20265 min read
feature-flagsci-cdprogressive-deliverydevops

The riskiest moment in any deployment isn't writing the code — it's the moment you flip the switch and expose it to real users. Feature flags change that equation entirely by separating the act of deploying code from the act of releasing a feature.

The Problem with Big-Bang Releases

Traditional deployment is binary: code is either live or it isn't. You merge to main, the pipeline runs, and every user gets the new feature at once. If something breaks, your options are rolling back the entire deployment or pushing a hotfix under pressure.

There is a better way — and it starts with separating deployment from release.

What Feature Flags Actually Are

A feature flag is a conditional wrapper around new functionality. At its simplest:

feature-check.ts
function getPaymentProcessor(userId: string): PaymentProcessor {
  if (isFeatureEnabled("new-payment-flow", userId)) {
    return new StripeV2Processor();
  }
  return new StripeV1Processor();
}

The flag's state is evaluated at runtime, not at build time. This means you can:

  • Deploy code without releasing it — the feature sits behind a flag, invisible to users
  • Roll out gradually — enable for 1% of users, then 5%, then 25%, watching metrics at each step
  • Kill a feature instantly — flip the flag off, no redeployment needed
  • Target specific users — enable for internal teams, beta testers, or specific regions

The Four Types of Flags

Not all feature flags serve the same purpose, and understanding the distinction matters for maintenance:

Release Flags

Temporary flags for shipping incomplete work. These are the most common and should have the shortest lifespan.

release-flag.ts
// Ship the incomplete checkout redesign behind a flag
// Remove this flag once the feature is stable
if (flags.isEnabled("checkout-redesign")) {
  return <NewCheckoutFlow />;
}
return <LegacyCheckout />;

Experiment Flags

For A/B testing. They're short-lived but need to track which variant each user sees:

experiment-flag.ts
const variant = flags.getVariant("pricing-page-test", userId);
 
switch (variant) {
  case "control":
    return <CurrentPricingPage />;
  case "variant-a":
    return <SimplifiedPricingPage />;
  case "variant-b":
    return <ComparisonPricingPage />;
}

Ops Flags

Kill switches for features with uncertain performance characteristics. These are the ones that save you at 3 AM:

ops-flag.ts
// If the new recommendation engine is overloading the database,
// flip this flag and we fall back to the static list
const recommendations = flags.isEnabled("ml-recommendations")
  ? await getMLRecommendations(userId)
  : await getStaticRecommendations();

Permission Flags

Long-lived flags that gate premium or beta features:

permission-flag.ts
if (flags.isEnabled("advanced-analytics", { plan: user.plan })) {
  return <AdvancedAnalyticsDashboard />;
}
return <BasicAnalyticsDashboard />;

The Hidden Cost: Flag Debt

Every feature flag is a branch in your code that doubles the testing surface. Two flags mean four possible states. Ten flags mean over a thousand combinations.

A simple convention worth enforcing:

flag-registry.ts
const FLAGS = {
  "checkout-redesign": {
    type: "release",
    owner: "payments-team",
    createdAt: "2026-03-01",
    removeBy: "2026-04-15",     // Flag must be removed by this date
    description: "New checkout flow with Stripe V2",
  },
} as const;

Implementation in CI/CD

Feature flags integrate naturally into a CI/CD pipeline:

.gitlab-ci.yml
deploy:
  stage: deploy
  script:
    - deploy-to-production
    - update-feature-flags --env production
  environment:
    name: production
    action: start
 
rollback:
  stage: deploy
  when: manual
  script:
    - disable-feature-flag checkout-redesign
    # No redeployment needed — the flag controls visibility

The pipeline deploys the code, but the feature flag controls whether users see it. Rollback becomes a flag toggle instead of a full redeployment.

Progressive Delivery in Practice

Feature flags are one piece of progressive delivery. Combined with other techniques, they create a deployment model where risk is minimized at every step:

  1. Deploy to production with the feature flagged off
  2. Enable for internal users — your team dogfoods it first
  3. Canary release — enable for 1-5% of traffic, monitor error rates
  4. Gradual rollout — increase to 25%, 50%, 100% over days
  5. Remove the flag — the feature is now the default

Each step includes an automatic rollback trigger: if error rates spike above a threshold, the flag is automatically disabled.

Key Takeaways

  1. Decouple deployment from release — deploy whenever you want, release when you're ready
  2. Start with ops flags — even if you don't use release flags yet, having kill switches for risky features is invaluable
  3. Set expiration dates on flags — every flag should have an owner and a removal deadline
  4. Monitor at each rollout stage — the whole point of gradual rollout is catching problems before they reach everyone
Share