Note: I decided to keep the context of this article to co-located teams. I believe the general principals also apply to open source projects and distributed teams, but those different contexts would need its own explanations (maybe in a future article).

I know these are bold statements, but it is time to speak up. Far too often feature branching is THE biggest bottleneck in the pipeline to production. Teams working towards increased deploy frequency, faster feedback loops and better quality cannot afford to batch their work into long-lived branches. Long-lived being more than one day.

The drawbacks of batching was proven by Toyota back in the 1950's and 60’s and detailed by Taiichi Ohno (1912–1990) in his book Toyota seisan hoshiki (1978), or Toyota Production System (translated 1988). Today known as Lean Manufacturing. Later proved to also apply to software development, and is today one of the core principles of DevOps.

What is wrong with feature branching?

In theory there is nothing wrong with feature branching. It is the way most teams practice feature branching that is problematic.

The common practice is to accumulate or batch work into long-lived branches without integrating with trunk frequently enough.

Proper use of feature branching would be to practice continuous integration as it was intended, by integrating your feature branch to trunk at least once per day. In my 20-odd long career, I have still not come across a team doing that.

In my work I see teams either doing long-lived feature branches or no feature branching at all – frequent integration of feature branches is not common practice.

So what are the consequences of doing long-lived feature branches?

We delay integration and hinder communication — basically we opt out of continuous integration No integration between feature branches, unless feature branches explicitly pull from each other, which is not a common practice. This is what Martin Fowler calls Promiscuous Integration (3rd sub-chapter). When we merge, we introduce many changes at once (large batch), which can be disruptive and in general harder to find and fix issues because of the amount of changes introduced. With Lean its proven that adding small changes incrementally over time is much more effective and provides better quality than batching work. We opt out of the other practices of continuous integration, like automated build, test, performance and security testing. Even though it is possible to have these performed on a branch, the value is limited, since it is not integrated with everything else. We delay internal feedback loops. Only when pulling from trunk will we locally integrate with outside changes. Note that even if we did pull from trunk on a regular basis, it is only internal to us — nobody else knows about our work. We delay external feedback loops. Only when we merge with trunk will others be able to see and integrate with our work, and get actual feedback from the integration. We exponentially increase merge complexity and increase the likelihood of creating conflicts with every new living branch We tend to avoid refactoring in fear of merge conflicts — which prevents paying down technical debt

Feedback, feedback, feedback

A common theme here is missing out on feedback. Often, missed feedback opportunities include valuable information that you could have used to make better decisions, preventing rework. Everyone and everything outside our batch will not see any changes before they all arrive at once, missing potentially valuable information that could have influence their decisions on their work.

Only when we merge a feature branch will we get feedback on the result of the integration. If it did not work out as expected, it is often too late, too much or both — to do something about. This is wasteful if we could have done something about it earlier, but actively (or passively) put ourself in a position where we could not.

Common efforts to improve feature branching

I’ve seen many attempts to work around the consequences listed above. Most commonly:

Pull and merge to/from feature branches frequently

Setup “continuous integration” for every branch

Every branch gets its own test environment

Let us walk through each in turn.

Pull and merge to/from feature branch frequently

This works. Especially if you do it continuously. But why work in a feature branch if you are continuously integrating anyway?

Setup “continuous integration” for every branch

This will let you execute build, test and the other CI practices, but you will still not integrate and get feedback of any integration issues, and you will still introduce a big batch of work at the end. Actually, in my experience, branches with their own dedicated CI tends to live even longer, making the problem worse.

Every branch gets its own dedicated test environment

I leave it for you as an exercise to figure this one out. A hint is integration.

Last but not least…

If you and your team do not notice friction with long lived feature branches, you are not moving fast enough. If/when you increase your deploy and feedback frequencies, feature branching will fight you.

And what is the alternative? In my opinion it is Trunk-Based Development which I wrote about earlier: