Trunk-Based Development: Shipping Safely Every Day
By Carlos Diaz · September 7, 2023 · 3 min read
Before trunk-based development, our release process involved a merge day that everyone dreaded - feature branches that had diverged from main for two or three weeks, merge conflicts that took hours to resolve, and a non-trivial chance that the 'fix' for one conflict silently broke something else. After moving to trunk-based development on a production microservices platform, that entire failure mode simply stopped existing, because nothing was ever allowed to diverge from main long enough to create it.
The practice itself is simple to state and demanding to sustain: every engineer merges small, complete changes directly to main at least once a day, and nothing lives on a long-lived branch. What makes it demanding is that it requires supporting infrastructure most teams underinvest in. Feature flags are the mechanism that decouples merging code from releasing a feature - unfinished work merges to main behind a flag that is off, so 'merged' stops meaning 'live for users,' and a half-built feature never blocks anyone else's changes from shipping.
A fast, reliable CI pipeline is the other non-negotiable piece, and it is the one teams most often skip. If the build takes forty minutes, or fails intermittently for reasons unrelated to the actual change, engineers rationally start batching changes to avoid running the gauntlet more than necessary - and batching changes is exactly the failure mode trunk-based development is designed to eliminate. We invested specifically in test reliability and build caching before asking the team to adopt smaller, more frequent merges, because asking for the practice without the infrastructure just produces frustration.
Branch-by-abstraction is the technique that makes trunk-based development work even for larger structural changes: instead of a long-lived branch that swaps an old implementation for a new one, you introduce an abstraction layer behind which both implementations can coexist, migrate callers incrementally behind that abstraction, and remove the old implementation only once nothing depends on it. This means even substantial refactors happen as a sequence of small, mergeable, revertible changes rather than one large branch.
The payoff is visible in incident response specifically: when every change is small, finding the commit that introduced a regression takes minutes instead of hours, reverting it is a single clean operation instead of a partial-revert puzzle, and the team's shared mental model of the codebase stays current because nobody is working from a three-week-old snapshot of main.
Key Takeaways
- Feature flags decouple merging code from releasing a feature, so unfinished work ships dark.
- A fast, trustworthy CI pipeline is non-negotiable - without it, teams rationally batch changes.
- Branch-by-abstraction makes even large structural refactors happen as small, revertible steps.
- Small, frequent merges make reverts trivial and root-causing incidents fast.
- The whole team keeps a shared, current mental model of the codebase.

