How Feature Flags Changed the Meaning of Done

Software code on screen with feature toggle configuration

Most engineering teams have a definition of “done.” It sits on a wall, or a Confluence page, or in the team’s verbal shorthand. Code written. Tests passing. Peer-reviewed. Merged. Deployed. Done. That sequence felt solid for years. Then feature flags walked in and pulled the last two steps apart like a rusted bolt shearing off.

Feature flags—also called feature toggles—let you wrap a piece of functionality in a conditional check so you can turn it on or off without deploying new code. On the surface, the mechanism is almost embarrassingly simple: an if statement that reads from a configuration store. But that simplicity hides a structural shift in what “done” actually means, and most teams haven’t caught up.

The Old Contract: Deploy Equals Release

Before feature flags became common, deployment and release were the same event. When your code hit production, users saw it. The pipeline had a single handoff point: code moves from “not in production” to “in production,” and that transition is what made a feature “done.”

This contract was clean but brittle. If something went wrong after deployment, you rolled back the entire deploy—yanking not just the broken feature but every other change that shipped with it. The risk equation was blunt: you bet the whole release on every individual feature inside it. Small wonder teams gravitated toward long-lived branches and big-bang releases. The cost of a mistake was high, so you padded the preparation phase until the risk felt tolerable.

The problem with that model isn’t just speed. It’s that “done” becomes a gate held hostage by unrelated work. Your feature might be complete, tested, and reviewed on Monday, but it doesn’t reach users until the release train departs on Thursday—and only if every other feature on that train is also ready. Your “done” was conditional on someone else’s timeline.

Team collaborating on deployment strategy

What Feature Flags Actually Do to That Contract

Feature flags break the assumption that deployment equals release. Once a flag wraps your feature, you can deploy the code to production with the flag set to false. The code runs in production; no user sees it. The release happens later, when you flip the flag to true.

This decoupling changes the risk profile of every deployment. You’re no longer betting that the feature works perfectly at the moment it reaches users. You’re betting that the code can sit dormant in production without breaking anything else—a much lower bar. Martin Fowler’s original taxonomy of feature toggles lays this out clearly: some toggles exist for release management, others for ops, others for experimentation. Each type introduces a different reason to separate deployment from visibility.

But here’s what most retrospectives skip: once you decouple deployment from release, the word “done” stops having a single definition. It fractures.

The New Layers of Done

With feature flags in play, “done” is no longer a binary state. It’s a progression through at least four distinct milestones:

1. Code Complete

The functionality is implemented, unit-tested, and passes review. The feature flag exists. The code is safe to merge but hasn’t been. This is the old “dev complete” checkpoint, and it still matters—but it’s no longer the final one.

2. Merged and Deployed (Flag Off)

The code sits in the main branch and has been deployed to production with its flag disabled. The feature is in production but not active in production. Many teams call this done because the deployment pipeline has processed the change. But calling it done here is premature: dormant code can still cause problems, and the flag itself may need adjustment before anyone turns it on.

3. Released (Flag On for a Subset)

The flag is enabled for a controlled group—internal testers, beta users, a percentage rollout. The feature is now visible and exercisable, but not universally. This is progressive delivery: you’re watching real traffic hit your feature, gathering signal, and deciding whether to widen the aperture.

4. Generally Available and Flag Removed

The flag is on for all users, and—this is the part teams forget—the flag code itself has been removed from the codebase. The feature is unconditional. This is the only point that matches the old definition of “done” in full. Until the flag is cleaned up, you’re carrying technical debt: dead configuration paths, testing complexity, and conditional logic that future readers will have to reason through.

Each of these milestones is a legitimate checkpoint. Pretending they’re all the same thing is how you end up with a flag graveyard—hundreds of toggles sitting in configuration files, nobody sure which ones are still needed, each one a small tax on every future change.

The Costs People Underestimate

Feature flags are not free. Every flag you add introduces combinatorial complexity into your testing surface. Two flags produce four possible states. Ten flags produce over a thousand. You can’t test every combination, so you make judgment calls about which paths matter—and those calls are themselves a source of risk.

Then there’s the configuration management problem. Where do flag values live? A database? A feature-flag service? Environment variables baked into the deploy? Each choice has different latency characteristics, different failure modes, and different permissions models. Get this wrong and you’re debugging why a flag changed in production at 2 a.m. while the on-call engineer stares at a diff that shows no code changes.

The most insidious cost, though, is conceptual. When “done” has four stages instead of one, your team needs shared language to communicate which stage a feature occupies. A ticket marked “done” that means “code complete but not deployed” creates a different expectation than one that means “flag removed, fully shipped.” Without explicit agreement on terminology, stakeholders will fill in their own definitions, and those definitions won’t match yours.

Developer monitoring feature flag rollout metrics

Rethinking Your Team’s Agreement

If your team uses feature flags, your definition of done needs to reflect that reality explicitly. Here’s what that looks like in practice:

  • Separate ticket states for each milestone. Don’t compress “code complete” and “flag removed” into a single status. Use distinct columns or labels so that anyone scanning the board can tell exactly where a feature stands.
  • Track flag lifecycle, not just flag existence. When a flag is created, there should be a corresponding task to remove it. Treat flag creation as a commitment, not a convenience. Every flag you add is a promise to clean it up later. The LaunchDarkly flag lifecycle documentation describes this pattern as a standard practice for mature teams.
  • Test the flag states that matter. You can’t test every combination, but you can test the paths that are actually exercised. When a flag is off, the code behind it is unreachable; when it’s on, the fallback path is unreachable. Write tests that match the states users will encounter, and document which combinations you’re deliberately not covering.
  • Make flag removal part of the definition of done. If your team’s DoD doesn’t include “flag removed and code unconditional,” you’re leaving the hardest part of the job unspecified. The cleanup is where the discipline lives. Anyone can add a flag. Removing it requires confidence that the feature is stable, that no configuration depends on the old behavior, and that future readers won’t need to reason through a conditional that no longer serves a purpose.

Why This Matters Beyond Convenience

The shift from “deploy equals release” to “deploy and release are separate events” isn’t just a process change. It’s a change in how we think about software delivery itself. When you can deploy incomplete work safely—knowing it won’t affect users until you decide it should—you make different architectural decisions. You write smaller, more incremental changes. You ship more often. You learn faster because you can expose work to real traffic sooner.

But those gains disappear if you treat “done” the same way you did before flags existed. A definition of done that stops at “code merged” is a definition that ignores the reality of how your software actually reaches users. A definition that stops at “flag turned on” is a definition that pretends cleanup isn’t part of the job.

Feature flags didn’t just add a tool to your pipeline. They changed the pipeline’s shape. Your definition of done needs to change with it—or you’ll spend years wondering why “done” features keep coming back as bugs, confusion, and dead code.

FAQ

Does every feature need a flag?

No. Flags are useful when you want to decouple deployment from release—typically for risky changes, experiments, or features that depend on external readiness (a partner launch date, a marketing announcement). If a change is small, well-understood, and low-risk, wrapping it in a flag adds overhead without proportional benefit. The discipline isn’t in flagging everything; it’s in knowing when the decoupling is worth the cost.

How long should a feature flag live?

Short-lived flags (release toggles) should be removed as soon as the feature has been fully rolled out and confirmed stable—usually within days or weeks. Long-lived flags (ops toggles, permission toggles) serve ongoing purposes and can stay indefinitely, but they should be documented, tested, and clearly owned. If you can’t explain why a flag still exists, it’s a candidate for removal.

What happens if you skip flag cleanup?

Dead flags accumulate into technical debt. Every flag adds conditional logic that future developers must read, understand, and work around. Configuration stores become opaque—no one knows which flags are active, which are safe to flip, and which control nothing. Testing complexity grows because each flag multiplies the number of possible code paths. Eventually, the cost of understanding the system exceeds the cost of cleaning it up, and by that point, no one remembers enough to clean it up safely. Regular flag removal prevents that spiral.

Related Post