~/blog/feature-flags-safe-deployments
Engineering2021·15 June 2021

Feature Flags: Enabling Safe and Reliable Deployments

Feature flags changed how we deploy at Freddie's Flowers more than any other single practice. Here's how we implemented them, what went wrong early on, and why I'd never run a production system without them.

feature-flagsdeploymentLaunchDarklyreleaseCI/CD

The Deployment Problem

Before feature flags, deploying new features meant one of two uncomfortable options:

Option A: Long-lived feature branches. Develop the feature in isolation, merge everything at once when it's "done". The merge is usually painful (diverged from main for weeks), the release is an all-or-nothing bet, and rollback means reverting the entire deployment.

Option B: Deploy to production immediately. The feature is "done" but needs user validation. If something goes wrong, you're rolling back for all users simultaneously, with pressure to do so quickly.

Feature flags offer a third option: deploy code continuously, but control feature activation independently.

How Feature Flags Work

A feature flag is a conditional in your code:

javascript
if (featureFlags.isEnabled('new-checkout-flow', userId)) { return renderNewCheckout(); } else { return renderLegacyCheckout(); }

The flag evaluation — true or false — is determined by a service you control. You can enable a flag for:

  • ·A specific list of user IDs (internal team testing)
  • ·A percentage of users (gradual rollout — 1% → 5% → 25% → 100%)
  • ·Users in a specific cohort (Beta users, UK users, paying subscribers)
  • ·Everyone (full release)

The code change (the if block) ships in your normal deployment. The feature activation is a configuration change — instantaneous, no deployment required.

What This Enables

Dark launching: Ship the code. Run the new code path in shadow for some users, but don't show the output. Validate performance and correctness in production without user-visible impact.

Percentage rollouts: Enable the feature for 1% of users. Watch your monitoring. If error rates, conversion rates, or latency look normal after 24 hours — roll to 5%. Then 25%. Then 100%. If something looks wrong at any stage — kill the flag. The rollback is instant.

Instant kill switch: Something is broken in production for some users. Kill the flag. The feature is off immediately — no deployment, no coordination, no pressure.

The Implementation at Freddie's Flowers

We implemented feature flags progressively. Initially, a simple database table — flag name, enabled boolean, user percentage. Evaluated on every request from a cached in-memory store.

For more sophisticated targeting (subscription tier, region, cohort), we moved to a hosted service. The principle was the same; the targeting capabilities were richer.

The Failure Modes

Flag proliferation: Six months in, we had 40+ active flags with unclear ownership. Start with a flag lifecycle policy: every flag has an owner, an expected removal date, and a Jira ticket to remove it once fully rolled out. Flag debt is real.

Test path coverage: When you have flags, you effectively have multiple code paths in production. Your test suite needs to cover both the "flag on" and "flag off" paths. We found flag-related test gaps during a major release that cost us time.

Performance: Flag evaluation adds latency if done naively. Cache aggressively. Evaluate flags in bulk at request start rather than on-demand throughout the request lifecycle.

Feature flags are now non-negotiable for me in any production system I lead. The deployment confidence they provide — knowing you can ship continuously and roll back instantly — changes the entire team's relationship with deploying software.