Trunk-Based Development at Scale
How very large engineering organizations keep thousands of developers on a single mainline — the automation that makes it possible, and the smaller-scale version that works without it.
Several of the largest software organizations — Google and Meta are the best-documented — run enormous engineering populations against a single mainline with very short-lived branches, and in the largest cases a single repository containing most of the company’s code. This is the opposite of the intuition that scale requires more isolation.
The practice is inseparable from the automation that supports it, and that relationship is the transferable lesson.
Why long-lived branches cost more than they appear to
flowchart LR
subgraph "Long-lived branches"
A1[Branch created] --> A2["Diverges for weeks"]
A2 --> A3["Merge conflicts grow<br/>superlinearly with time"]
A3 --> A4["Big-bang integration"]
A4 --> A5["Integration bugs appear<br/>all at once, far from<br/>the change that caused them"]
end
subgraph "Trunk-based"
B1[Small change] --> B2["Merged within hours"]
B2 --> B3["Conflicts are small<br/>and immediate"]
B3 --> B4["Continuous integration<br/>in the literal sense"]
B4 --> B5["Problems surface next to<br/>the change that caused them"]
end
The cost of a branch is roughly proportional to the square of its lifetime, because divergence accumulates on both sides. A branch open for a day is nearly free; one open for six weeks is a project in itself.
Trunk-based development is also a prerequisite for continuous delivery in any meaningful sense. If the mainline is not always releasable, no amount of deployment automation gets you continuous delivery — you have automated deployment of a batch process.
What makes it possible at scale
Trunk-based development does not work by asking people to be careful. It works because a set of automated mechanisms make it safe.
| Mechanism | Function |
|---|---|
| Merge queue | Tests each change against the result of merging it, serialising integration so trunk never breaks |
| Fast, reliable pre-submit tests | Verdict in minutes; flakes eliminated aggressively |
| Affected-target build system | Only tests reachable from the change are run, keeping runtimes flat as the codebase grows |
| Feature flags | Incomplete work merges to trunk, disabled, so branches are not needed for incompleteness |
| Automated large-scale change tooling | One person can safely change an API across the entire codebase |
| Strong ownership metadata | Reviewers routed automatically per directory |
| Automated formatting and static analysis | Removes an entire class of review discussion |
The merge queue is the linchpin. Without one, testing a change against trunk as it was when the branch started is not the same as testing the merge result, and trunk breaks intermittently in a way that is nobody’s obvious fault.
sequenceDiagram
participant A as PR A
participant B as PR B
participant Q as Merge queue
participant T as Trunk
A->>Q: enqueue (tested green against trunk@100)
B->>Q: enqueue (tested green against trunk@100)
Q->>Q: build speculative merge: trunk@100 + A
Q->>T: tests pass → merge A (trunk@101)
Q->>Q: build speculative merge: trunk@101 + B
alt B conflicts semantically with A
Q-->>B: reject, do not merge, and notify the author
Note over T: Trunk stays green
else compatible
Q->>T: merge B (trunk@102)
end
Feature flags as the branching mechanism
Trunk-based development does not mean shipping unfinished features. It means the isolation happens at runtime rather than in version control.
Incomplete work merges continuously behind a flag that is off. It is integrated, compiled, and tested with everything else from the first day, so the integration problems appear immediately rather than at the end. When the feature is ready, enabling it is a configuration change — as described in Deployment Strategies.
The discipline this requires: flags must be removed. A large codebase with thousands of stale flags has a combinatorial state space nobody can reason about. Every flag needs an owner and an expiry, and expired flags should surface as work.
Large-scale changes
The monorepo variant of this practice gains a capability that is genuinely hard otherwise: one engineer can update an API across every consumer in the company, atomically, with tooling that generates, splits, and lands the change.
In a multi-repository world the same migration is a coordination programme lasting quarters, because you cannot see all the consumers and cannot change them together.
This is the strongest argument for a monorepo, and it comes with a correspondingly strong requirement: it only works with the build and test infrastructure that makes a repository of that size navigable. Adopting the repository structure without the tooling produces the costs and none of the benefit.
What transfers
Transfers to almost any team, immediately:
- Short-lived branches. Merge within a day or two. This is the core of the practice and needs no special infrastructure.
- Feature flags instead of feature branches. Modest tooling; large effect.
- A merge queue. Widely available in hosted platforms now, and it is the single highest-value addition for a team of any size that has more than a couple of concurrent changes.
- Aggressive flake elimination, because trunk-based development depends entirely on trusting the test signal.
- Trunk is always releasable as a non-negotiable rule.
Requires investment:
- Affected-target test selection, which matters once full suite runtimes exceed the ten-minute budget.
- Automated large-scale change tooling.
Does not transfer:
- The monorepo itself, unless you are prepared to build or buy the supporting build system. The published experience is consistent that the repository structure is a consequence of the tooling investment, not a shortcut to its benefits.
Common objections, answered
“Our tests are too slow for this.” Then the test suite is the problem to fix, not the branching model to avoid. Long-lived branches are a workaround for slow feedback, and they make the feedback slower.
“We need branches for release stabilisation.” A release branch cut from a green trunk and receiving only cherry-picked fixes is compatible with the practice. A release branch that receives weeks of development is not.
“Our developers will break trunk.” This is what the merge queue prevents. If trunk breaks regularly, the automation is missing, not the discipline.
“We are regulated and need approval before merge.” Code review is the approval, and it happens before merge. This is compatible; see Automating Change Management.
Adoption checklist
- Branches live for days at most; the target is hours.
- A merge queue tests the merge result, not the branch in isolation.
- Trunk is always releasable, treated as a non-negotiable rule.
- Incomplete work merges behind flags rather than living on branches.
- Flags have owners and expiry dates, and stale flags surface as work.
- Flaky tests are eliminated, not tolerated.
- Pre-merge feedback arrives within roughly ten minutes.
- Release branches, if used, receive only cherry-picked fixes.
Last updated 19 Aug 2026, 00:00 UTC.