For how important branching strategies are, we don’t talk about them as often as we should. We pick our approach, alongside the core languages and tools, at the start of a project and then never think about it again. At most, we might make minor tweaks around the edges — how many reviewers need to approve a pull request, what git tags we use, when our pipelines are triggered, etc. But we rarely check whether Trunk-Based Development or Feature Branching is still working for us.
Software development is not static. Our teams, projects, and the approaches we take are constantly evolving and changing, and failing to update our branching strategy in response results in a lot of pain for the development team. Neither branching strategy is inherently better than the other; each has different circumstances where it shines. Multiple times in my career, I have found myself part of teams locked into one particular strategy when the other would have been a significantly better fit for their situation.
The impact our branching strategy has on the development team shouldn’t come as a surprise. After all, our branching strategy is the primary method we use to communicate and collaborate with other developers. Broadly, the problems we see with branching strategies fall into three categories: the strategy is not a good fit for the team or project, is not implemented well, or lacks developer buy-in. Below, I will cover situations where each strategy is strongest, cover issues you might have with your current implementation, and then provide guidance on when and where to use each.
An overview of branching strategies
All branching strategies fall into one of two categories — Trunk-Based Development or Feature Branching; everything else is just additional process layered on top of these two core philosophies. The primary difference between Trunk-Based Development and Feature Branching is how frequently developers commit or merge into the main branch. In Trunk-Based Development, we prioritize having all developers working against the same version of the codebase. The development team regularly pushes changes to achieve this, trusting in high test automation to keep the main branch in a good state.
Meanwhile, Feature Branching keeps changes isolated on branches until they are fully complete. Each branch contains a different version of the codebase that is only reconciled with the main branch after it has passed through all quality gates and checks. Feature-Branching emphasizes caution, keeping code away from Main until it is completely ready.
Diversity of implementations makes it challenging to have conversations about branching strategies. Developers tend to conflate their baggage from processes taken in previous roles with the underlying strategy. Needing an entire day to merge a feature branch isn’t a mark against Feature Branching as a strategy. Instead, it is a problem with how that particular team implemented Feature Branching.
When to use Trunk-Based Development
Trunk-Based Development is strongest in environments that already have the conditions that guarantee a high level of quality. For example, you have a mature team of experienced developers who prioritize writing quality code. Your project has a high enough level of test automation that developers can commit code to Main without a manual tester needing to check it first. Your team is carrying low levels of technical debt, and your application has few hidden traps. Feature Branching’s additional quality controls are unneeded and will likely slow your team down in this scenario.
When you need to build or iterate quickly, Trunk-Based Development is very effective; perhaps you are creating a new application, performing an in-place rewrite, or making extensive changes to a single component of the application over an extended time. In these cases, the application undergoes a lot of change across a small surface area, and the team needs to be able to reconcile these changes as soon as possible.
Trunk-Based Development simplifies development: you can pick up a task and start it without having to consider all of the various in-progress states of the application in other branches. In practice, this frees up a lot of mental clutter and distractions, leaving you more energy to focus on the work at hand. The smaller, more frequent commits reduce each change’s risk. CI/CD and DevOps allow for more frequent deployments, making problems easier to identify and quicker to fix.
Many current best practices line up and reinforce Trunk-Based development: code ownership, automated testing CI/CD, and DevOps. Rather than Feature-Branching’s constant baton-passing of ownership, each developer owns their work through the entire software development life cycle. When you’re committing directly to master, you can’t rely on manual testers to prevent you from breaking the build, so keeping your automated testing up to date is mandatory. Trunk-Based Development encourages you to keep strengthening your automation layer instead of assigning manual tasks.
There are many reported correlations between high-performing teams and Trunk-Based Development. However, adopting Trunk-Based Development is also be a chicken and egg problem. There is a lot of potential momentum and quality to be gained, but first, you require a mature team already working with momentum and high quality levels. The potential rewards are high, but Trunk-Based Development alone won’t magic your team into high-performing.
When to use Feature Branching
Feature branching works well in environments where mistakes are expensive. When you have many teams working on a single application, you need to minimize the impact a single faulty commit can have. This cost is even higher for teams distributed across locations and time zones. Someone breaking the build before they leave for the day can result in entire teams getting held up. Industries such as health, finance, and aerospace, have a much lower tolerance for mistakes making their way out of development and need many quality checks baked into every development stage.
Feature Branching is also helpful in environments where mistakes are not just impactful, but likely. Perhaps you are rapidly expanding, and your teams mainly consist of new hires, juniors, or short-term contractors — people who lack experience with your application and need guidance to avoid mistakes. Perhaps you need to maintain an aging application with a lot of technical debt, few safety nets, and fragile architecture. In both of these cases, slowing down and adding more quality steps is a good idea.
When multiple teams share the same project or repository, Trunk-Based Development often falls over because it requires a level of communication that is impractical across team boundaries. Not every software project can be broken down into isolated components, and in these cases, you may find Feature Branching a better fit.
The costs of poorly done Trunk-Based-Development
Trunk-Based Development’s most tangible benefit is reducing context-shifting and allowing the developers to focus on a single state of the application. The risk you take is an increase in distraction. Without a high level of test coverage and automation, you may be frequently disrupted by “broken builds.” These could be obvious changes that prevent the build from compiling, but more frequently are subtle changes in behavior or bugs that don’t get discovered until many commits later. Depending on the severity, you might find yourselves frequently downing tools to figure out who broke the build and who needs to fix it. Disruption from large and unexpected changes requires you to constantly adjust your code and mental models as changes come in. Feeling like you’re constantly working on an unstable foundation adds considerable stress and hinders your team’s ability to expand as more developers lead to more disruptions.
More developers are familiar with Feature Branching than with Trunk-Based Development; it’s conceptually more straightforward and practiced far more widely. The development team might not fully buy into the idea of Trunk-Based Development or approach it in a naive fashion. Learning how to break down a task into multiple small commits requires a significant mindset shift. You might find the development team making large Feature Branch-sized changes at a slow Feature Branch frequency, but in single commits to Main. The team is still developing in a Feature Branching style without using branches. Taking this approach offers the worst of both worlds — the large changesets introduce more risk without Feature Branching’s typical safety measures.
In Trunk-Based environments, you must pay constant attention to how frequently you can commit changes. Whether held up by code review turnaround times, or difficulties swarming and coordinating on shared features, you risk starting negative feedback loops where commits start to grow larger and larger. The worst situation would be each developer simultaneously working on different long-lived features, killing team productivity.
The costs of poorly done Featuring Branching
Ineffectively implemented, Feature Branching has its own costs. Branches frequently get stuck, spending a long time in development and then needing to pass through all of the sign-off gates on the way back to the main branch. These hurdles cause delays for all dependent work, and while ideally branching, features and tasks would be independent, in my experience, they rarely are. Attempting to prevent the developers from stepping on each other’s toes leads to the team working on too many long-lived simultaneous feature branches.
Developers become paralyzed knowing that an incoming branch will fundamentally change the current state of the application. It does not feel worthwhile writing new code knowing that in a couple of days, it’ll need rewriting to reflect the new state of the application. You might as well find another task while waiting for that branch to merge in and change everything. In comparison, changes in a Trunk-Based environment are integrated quickly and in smaller, more digestible pieces, allowing you to immediately shift to working in the new state of the application.
Without a great deal of care, Feature Branching encourages additional checks, gates, and sign-offs to be added to the process, dramatically increasing each branch’s lifetime. Paradoxically long-lived branches with a lot of quality checks end up reducing overall quality. They increase the chance of complex merges either introducing subtle mistakes or breaking entire features. Developers find their mental resources exhausted as they waste energy context-switching and jumping between different models of the application’s structure and behavior. Important refactors have to be abandoned as by the time they are ready, merging back in has become impossible. Manual testers testing on branches waste a lot of time testing code that is already out-of-date and separate from the code it is about to rejoin.
Feature Branching risks leaving a team without a sense of shared ownership. Everyone has separate work that they do in their separate branches. Developers barely know what others are working on until it is merged. Turnaround time for code reviews is slow because reviewers aren’t invested, and the reviews are enormous and time-consuming. In contrast, in Trunk-Based Development, most changes happen frequently and in a single location, actively encouraging shared ownership of the main branch and leading to developers invested in all incoming changes.
The problem of buy-in
For proponents of each approach, some of the above issues may seem unfairly attributed to the branching strategy. Rather than being problems with each strategy’s core, they’re problems with how it has been implemented and enacted by your team. If everyone were doing it properly, they would find that your favorite strategy would indeed be the best branching strategy.
However, just as important as the approach itself is how you perceive and work with it. Trunk-Based Development requires a mature team with a high level of buy-in, and without both, you get stuck in a middle-ground that manages to take the worst of both branching strategies. Meanwhile, with the slower, more methodical, and careful pace of Feature Branching, you will find impatient developers taking shortcuts and circumventing the quality controls you are trying to put in place.
Sometimes a change in strategy is required so that your branching lines up with how you want to develop software; alternatively, your actual problem might be buy-in. The development team wants to use one strategy but feels stuck with the other. Developer resistance and reluctance might signal that you need to do more work to achieve buy-in and set the right mindset for the current approach. On the other hand, perhaps the developers see something that you don’t. The problems may be symptomatic of a misalignment between the branching strategy and the team’s day-to-day experience. Branching strategies aren’t one-size-fits-all, and each project and team needs the flexibility to implement branching to fit their needs. Your greenfields team shouldn’t be constrained by the process and requirements of the team maintaining your legacy application.
Should you make the switch?
If you can’t apply one strategy correctly, you might switch only to find yourself with another failed implementation. There is a high cost to making a permanent change in strategy; developers need engagement, training, and guidance through the new patterns and mindsets. Significant changes will require manager approval and sign-off. You need to redesign and rebuild your build, test, and release processes and pipelines to favor your new strategy. You face months of discovering and resolving a variety of kinks and unexpected follow-on effects.
In light of the difficulty of a permanent shift in strategy, I suspect we overestimate the difficulty of a temporary switch. It’s a lot less risky to accept the baggage of whatever you are currently doing and try out some of the elements of the other strategy. For example, if you have started a new greenfields project, you might decide to focus on having a single mental model of the application and stripping away all of the cruft that was keeping your branches alive for too long. After all, if your application is months away from the customer’s hands, it doesn’t need detailed, disruptive testing after every change. Look for opportunities to “try before you buy” and focus on experimenting with the new mindsets without dramatically altering your process first.
So perhaps you suspect that your current strategy isn’t working for your team. The question is, do you switch processes or try to improve what you’ve already got? Branching strategies are tricky, and you are unlikely to please everyone.
Trunk-based Development versus Feature Branching isn’t about personal preference, nor is it about what you have had success with in the past. The choice has to be made based on your current project and team.
Use Features Branching when:
- You have many teams working on the same project/codebase.
- There is a high ratio of inexperienced to experienced developers, such as many new hires, juniors, or short-term contractors.
- Manual testing is a requirement for keeping the main branch releasable.
- The individuals or teams contributing to the code base are distributed across multiple time zones.
- Frequent “broken builds” require the development team to down tools and investigate why the application is no longer working.
- Changes occur across the application’s breadth, and different features and branches rarely touch the same area.
Use Trunk-Based Development when:
- The project has a robust suite of automated tests, including integration and end-to-end tests.
- Teams have ownership over their own smaller projects and services.
- A feature can go from “dev complete” to merged in less than half a day — any manual testing or sign-offs are either completed within that time frame or not required.
- You frequently find yourself making branches off branches or integration branches.
- The development team has to sneak changes into unrelated branches because they are too difficult to get in by themselves.
- Most changes are concentrated within a relatively small area of the application.
- Refactors have become discouraged due to their tendency to get stuck on a branch for so long that merging back in is difficult and risky.
- Bottlenecks in your process are frequently causing branches to get stuck.
- You have a rapidly changing project, are building a new project, or rewriting an existing project or component.
Software projects are complex, living entities, and our processes need to be able to keep up. Whether we outgrow a strategy or external pressures force us to leave it behind, the most important thing is not to let dogmatic personal preferences interfere with our team’s ability to continue delivering working software.