Feature Flags Over Branches

First published at Wednesday, 22 July 2026

Feature Flags Over Branches

This is a blog article out of a series where I reflect on what I learned during funding, growing (, and selling) SaaS product companies. While I write those things down for myself, since I believe self-reflection helps me learn, I also want to share my thoughts with anybody who might be interested. An overview of all related topics can be found below. I have a bunch of these blog posts lined up, so you also might want to follow me if you like this one.

A while ago, when I wrote about when to use microservices, I said in passing that if you do not want a feature live yet you hide it behind a feature flag, and that I would write more about that. This is me making good on it, and the rule fits in one breath: develop on main, and use feature flags. Branches live for days, not weeks. Most of what people build to survive long-lived feature branches fixes a problem they did not have to create.

The failure mode this avoids

Picture the situation feature branches lead to. A team of any real size has several large features in flight at once, each on its own branch, each living for weeks. Every one of them drifts further from main every day. When they finally land, you get the merge conflicts you have been dreading, and resolving them by hand is both tedious and exactly the kind of work where you quietly introduce bugs. The longer the branch, the worse the reckoning.

What I find telling is what teams reach for to escape this. I have watched people split into separate repositories, carve the system into services, or, in the worst case I have seen, go to micro-frontends, all so that parallel work stops colliding. That is solving a process problem with a structural split, the same reflex I argued against for repositories: reaching for an expensive, hard-to-reverse boundary to buy something a change in workflow gives you almost for free. The collisions are not telling you to break up the system. They are telling you to stop keeping work on long-lived branches.

The answer is to integrate constantly. Everyone works against main, merges small changes often, and resolves the only-ever-tiny conflicts as they go instead of saving them up for one big painful merge. Which raises the obvious question: how do you do that with work that is not finished?

Two different things called feature flags

This is where feature flags come in, and it is worth separating two uses that get lumped together, because they have different lifetimes and different risks.

The first is roll-out. You decouple deploying code from releasing a feature. The code ships to production behind a flag that is off, so an unfinished feature can sit on main, fully integrated, invisible to users. When it is ready you turn it on, gradually if you like, for a cohort or a percentage first. Deploying and releasing become separate decisions made by separate people at separate times. This is the default use, and on a product team it should be close to automatic.

The second is customer-specific behaviour, and it is a different animal. A SaaS platform should not really have per-customer features, because every one of them is a permanent fork in behaviour you carry forever. But in practice you will sometimes have to. The discipline is keeping the count brutally low. On one platform we held it to a single customer-specific flag for a long time, and guarded that number deliberately, because the second and third are how a product quietly turns into a pile of bespoke installations wearing a trenchcoat.

Conflating the two is where flags get their bad reputation. A roll-out flag is meant to die: you remove it once the feature is fully released. A customer flag lives long, so you ration them. Treat them the same and you get the worst of both.

When the change is too big to hide

The fair objection is that some changes are too large or too structural to sit behind a simple on/off flag while half-built. Replacing a core component is not a matter of one if.

The technique for that is branch by abstraction, which I wrote about years ago in the context of shipping new code in a legacy codebase. You introduce an abstraction in front of the thing you are replacing, build the new implementation behind it on main, flag it, switch traffic over once it holds, and delete the old path. The system stays integrated and shippable the entire time, and the new work is exercised in production behind the flag before it carries load. In that post the new mail parser was feature-flagged to a single new customer first, which is both uses of flags meeting in one move: a roll-out flag that also happened to scope the change to one customer while it proved itself.

The catch, stated honestly

Flags are not free, and I would be selling you something if I pretended otherwise. An unremoved roll-out flag is debt. Let them accumulate and you recreate, in your flag configuration, exactly the combinatorial explosion you escaped by abandoning branches: a tangle of states, some combinations never tested, nobody sure what is safe to remove. The work the long-lived branch would have cost you up front comes back as the discipline of cleaning up flags after.

So the rule has a second half: remove roll-out flags promptly once the feature is fully live, and keep customer flags near zero and owned on purpose. Do that, and the flag configuration stays small enough to reason about. Skip it, and you have traded one mess for another.

Summary

Develop on main and keep branches measured in days. Use feature flags to decouple deploying from releasing, so unfinished work integrates continuously instead of rotting on a branch, and reach for branch by abstraction when a change is too big to hide behind a switch. Keep roll-out flags short-lived and remove them, and hold customer-specific flags close to zero. When parallel work starts colliding, that is a signal to integrate more often, not to split the system apart to keep the collisions away.

Related topics

In the context of the lessons I have learned during my last years of work, there are several blog posts. This list will be updated with every additional blog post I write (the titles might still change while writing about those topics):

Software Architecture
Remote culture
Leading tech teams
Collaboration with your product team(s)

Subscribe to updates

There are multiple ways to stay updated with new posts on my blog: