Feature Flags vs. Branches: The Ultimate Showdown
Feature Flags vs. Branches
At some point, every development team has to decide: do we use long-lived feature branches, or do we use feature flags to hide unfinished work on main? It’s not really an either/or question, but let’s pretend it is for the sake of argument.
The Case for Branches
Branches are comfortable. You create a branch, do your work in isolation, and merge when you’re done. Your changes don’t affect anyone else until you’re ready. The main branch stays clean. Easy.
The problem starts when branches live too long. A feature branch that’s open for two weeks starts to drift from main. After a month, merging it is a multi-day ordeal. Everyone’s stepped on each other’s changes, and half the original context is gone. The longer a branch lives, the more painful the merge becomes.
For more on why long-lived branches are a problem, see Martin Fowler’s take.
The Case for Feature Flags
Feature flags let you integrate early and often. You merge your incomplete feature to main behind a flag, which means you’re continuously integrating with everyone else’s work. No drift. No integration hell. Just a clean history and a flag that’s set to off.
The tradeoff is that your unfinished code is sitting in the production codebase. If the flag logic is buggy, you’ve shipped a bug to prod — it’s just a hidden one. And as discussed in the pitfalls post, flags require discipline to clean up.
Here’s a simple example of what flag-gated code looks like in C#:
if (await featureManager.IsEnabledAsync("NewCheckoutFlow")) {
return RedirectToAction("NewCheckout");
} else {
return RedirectToAction("OldCheckout");
}
Not complicated. But multiply that by thirty features and six months, and you start to see why cleanup matters.
Merging Conflicts
Branches love merge conflicts. The longer they live, the more conflicts accumulate. Feature flags don’t create merge conflicts — they just create runtime complexity. Both are real costs; they’re just paid at different times. Merge conflicts hurt now. Flag complexity hurts later.
Deployment and Release
With branches, deployment and release are tightly coupled. You can’t ship until the branch is merged. With feature flags, you decouple them entirely. The code ships whenever you’re ready; the feature releases when the flag is flipped. That’s a significant operational advantage if you’re doing continuous delivery.
The Right Answer
Use both. Short-lived branches for daily development, feature flags for controlling what users see. Merge to main frequently, flag unfinished work, and clean up flags when features are done.
Don’t use long-lived branches as a substitute for feature flags. Don’t let flags accumulate indefinitely. And don’t blame me when prod goes down.
Branches give you isolation. Feature flags give you control. You still have to write the code.