Feature Flags Done Right
Feature flags decouple deploy from release — but unmanaged they become tech debt. How to use them for safe rollout without the mess.
Deploy is not release
Feature flags exist to break the most dangerous assumption in software delivery: that shipping code and exposing a feature have to be the same event. With a flag, you deploy code in a dormant state and decide separately, at runtime, who sees it. That separation is what makes progressive rollout, instant rollback, and trunk-based development possible. It is also, used carelessly, one of the most reliable sources of long-term tech debt a codebase accumulates. The technique is simple; the discipline around it is what teams get wrong.
Know which kind of flag you are creating
The word flag covers several tools with very different lifespans, and conflating them is the root of most flag mess. Before you add one, decide which it is:
- Release flags hide work in progress so it can merge to the main branch unfinished. These are temporary by definition and must be deleted the moment the feature is fully live.
- Operational flags are kill switches for risky subsystems — a lever to shed load or disable a flaky integration under pressure. These live as long as the subsystem does.
- Permission flags gate features by plan or entitlement. These are really product configuration and belong with your entitlements model, not in the same bucket as throwaway release toggles.
The lifespan determines the rules. A release flag that outlives its release is a bug; an operational kill switch that someone deletes for tidiness is an outage waiting to happen.
Roll out in widening circles
The payoff of flags is the controlled rollout, and the pattern is a progression of widening exposure. Turn the feature on for your own team first, then a small internal cohort, then a single-digit percentage of real users, watching your error and latency metrics at every step. If the graphs stay healthy you widen; if they spike you flip the flag off and you are back to safety in seconds, with no deploy, no rollback pipeline, no incident bridge.
A rollback that takes a deploy is a rollback your team will hesitate to trigger. A rollback that takes a toggle is one they will use the instant they should.
This is why flag changes must be near-instant and must not require a code deploy to take effect. A flag you can only change by shipping is barely better than no flag at all.
Test the paths that actually ship
Every flag doubles the number of code paths, and your tests have to keep up or your coverage quietly becomes fiction. The trap is testing only the on state because that is the feature you are excited about, while the off state — the one currently in production for most users — goes unverified. At minimum, test both states of any flag that gates meaningful logic. Keep the number of simultaneously live flags small for the same reason: a handful of independent flags multiplies into a combinatorial explosion of states no test suite realistically covers.
Delete flags like you mean it
The single habit that separates teams who benefit from flags from teams drowning in them is removal. A release flag has a job, and when the feature is fully rolled out, that job is done — the flag and its dormant branch are now dead weight that confuses every future reader of the code. Put an expiry on every temporary flag at creation, review stale flags on a schedule, and treat a months-old release toggle as the debt it is. The flag system should trend toward empty of release flags, not accumulate them like sediment.
Govern who can flip what
A flag is a production change that bypasses your deploy pipeline — that is its whole value, and also its risk. The same toggle that lets you dark-launch a feature lets someone disable billing for every customer with one click, so the system that serves flags needs the controls you would put on any production action. That means an audit trail of who changed which flag and when, permissions that match the blast radius, and a way to see at a glance the current state of every flag in production.
- Log every flag change with actor and timestamp, so a mysterious behaviour shift can be traced to the toggle that caused it.
- Gate high-impact flags — kill switches, anything touching money or data — behind tighter permissions than a cosmetic rollout toggle.
- Keep flag configuration reviewable, ideally in version control or a system with change history, rather than as ephemeral state only the dashboard knows.
A feature flag is an undeployed production change. Treat the flag system with the same seriousness as the deploy pipeline it is bypassing.
Without this, flags trade one risk for another: you gain safe rollout but lose the traceability that a deploy gives you for free. Good governance puts that traceability back, so the speed of a toggle does not come at the cost of knowing what changed.
How BSH can help
BSH Technologies helps teams adopt feature flags as a delivery capability rather than a liability — clear flag taxonomy, progressive rollout wired to real metrics, instant kill switches for the risky paths, and a removal discipline that keeps the codebase clean. We have untangled flag-saturated systems and built the practices that stop the mess recurring. If your toggles have become permanent fixtures, let us help you make deploy and release two decisions again.
From the blog
View all postsDesigning Multi-Tenant SaaS That Scales
Choosing an isolation model, keeping tenant data separate, and dodging the noisy-neighbour and migration traps that bite SaaS later.
Hitting Green Core Web Vitals in Next.js
A practical guide to LCP, INP and CLS in Next.js — image handling, font loading, the App Router boundary, and costly third-party scripts.