Trunk-Based Development vs GitFlow: The Debate Settled
GitFlow dominated for a decade. Trunk-Based Development is how high-performing teams actually ship. Here's why, and how to make the switch.
Few topics generate more heated debate in engineering teams than branching strategy. GitFlow has been the default for so long that questioning it feels heretical. But the data from the DORA research program and the daily practice of high-performing engineering teams points clearly in one direction.
Let’s settle the debate.
GitFlow: The Strategy
GitFlow, introduced by Vincent Driessen in 2010, defines a strict branching model:
main ───────────────────────────────────────→
↑release ↑hotfix
develop ───────────────────────────────────→
↑feature-a ↑feature-b
feature/a ──────────────→
feature/b ──────────────────────────→
main: production-ready code onlydevelop: integration branch for next releasefeature/*: one per feature, branched from developrelease/*: release preparation branchhotfix/*: emergency fixes on main
It was designed for projects with scheduled releases and versioned software — open source libraries, mobile apps with app store releases, on-premise enterprise software.
GitFlow Problems
Long-lived branches create merge hell: a feature branch open for two weeks diverges significantly from develop. Merging becomes a multi-hour ordeal.
Integration is deferred: developers only discover integration issues when merging, not while building. The longer the branch lives, the worse the merge conflict.
Slow feedback loops: code in a feature branch isn’t tested against other features until merge. Bugs compound.
Overhead: maintaining develop, release, and hotfix branches adds complexity with little benefit for teams doing continuous delivery.
Trunk-Based Development
In Trunk-Based Development (TBD), everyone commits to a single shared branch (usually main) at least once a day. Feature branches exist but live for hours to days, never weeks.
main ──●──●──●──●──●──●──●──●──●──→
↑A ↑B ↑A ↑C ↑B ↑A
Every commit to main goes through CI. Features are hidden behind feature flags while they’re being built. Production releases are a subset of main, not a branch merge.
Why TBD Works
Continuous integration is real: when you commit to main daily, integration issues surface immediately while they’re small, not after two weeks when they’re catastrophic.
Smaller, safer changes: daily commits force you to break work into smaller increments. Smaller changes are easier to review, test, and roll back.
Feature flags replace branches: incomplete features are hidden at the application layer, not the git layer. The code is deployed; the feature isn’t visible.
Faster feedback: your code runs against production CI immediately. You know within minutes if something broke.
The Numbers
The DORA research (now part of Google Cloud’s DevOps Research and Assessment) consistently shows:
Elite performers deploy multiple times per day, have change failure rates below 5%, and restore service in under one hour. They overwhelmingly use trunk-based development.
Low performers deploy once a month or less, with failure rates above 15%. They overwhelmingly use long-lived feature branches.
GitFlow isn’t disqualifying, but it tends to enable the behaviors that correlate with low performance: batching changes, deferring integration, and treating deployment as a risk rather than a routine.
Making the Switch
Step 1: Shorten branch lifetimes
Don’t delete GitFlow overnight. Start by enforcing a rule: no branch older than 2 days. Branches that can’t merge in 2 days need to be broken into smaller pieces.
Step 2: Add feature flags
Feature flags are what make TBD safe. Introduce them for any significant feature that takes more than a day to build.
if (featureFlags.isEnabled('new-search')) {
return newSearch(query);
}
return legacySearch(query);
Step 3: Invest in CI
TBD only works with fast, reliable CI. A 30-minute CI pipeline makes daily commits painful. Get it under 10 minutes, ideally under 5.
Step 4: Move to short-lived branches
Replace long-lived feature branches with short-lived ones (< 2 days). Use them for code review, then merge and delete.
# Short-lived branch workflow
git checkout -b feature/add-search-filter
# ... make changes, get review ...
git checkout main
git merge feature/add-search-filter
git branch -d feature/add-search-filter
git push
When GitFlow Still Makes Sense
TBD is the right choice for teams doing continuous delivery or deployment. But GitFlow still makes sense in specific scenarios:
- Versioned open source libraries: you maintain v1.x, v2.x, and v3.x simultaneously, each with their own release branches and hotfix process
- App store releases: iOS/Android apps are reviewed and released on Apple/Google’s schedule, not yours
- Highly regulated industries: where a change must go through a formal approval process before release
If your deployment model doesn’t allow continuous delivery, GitFlow’s release management structure has genuine value.
Summary
| GitFlow | Trunk-Based Dev | |
|---|---|---|
| Branch lifetime | Weeks–months | Hours–days |
| Integration point | Merge to develop | Daily commits to main |
| Incomplete features | Feature branches | Feature flags |
| Release cadence | Scheduled | Continuous |
| Merge conflicts | Frequent, large | Rare, small |
| DORA correlation | Low performers | Elite performers |
| Best for | Versioned software | Continuous delivery |
Key Takeaways
- GitFlow was designed for scheduled releases and versioned software — not continuous delivery
- Trunk-Based Development correlates with elite engineering performance per DORA research
- Feature flags replace long-lived feature branches as the way to hide incomplete work
- The switch requires fast CI, a feature flag system, and a culture of small commits
- GitFlow still makes sense for versioned open source or app-store-constrained releases