A three-week feature branch is three weeks of divergence. The merge at the end is not one conflict but an accumulation: someone renamed a function you built on, the interface you extended changed shape, and a dependency moved. The work of reconciling is real engineering, it is unplanned, and it happens at the worst moment — when the feature was supposed to be finished.
Trunk-based development removes the accumulation by never letting it build: branches live hours or a day, everything integrates into the main line continuously, and incomplete work ships dark behind a flag rather than waiting in a branch.
The objection, and the answer
The objection is always the same: how do you avoid shipping unfinished work. The answer is that you separate deploying from releasing. Code that is not ready is behind a flag, or is a component nothing routes to yet, or is a new code path the old one still takes precedence over. It is in production, integrated and tested against everything else, and invisible.
The second objection is quality — that review and testing need a branch to happen in. They do not. They need a small change, which is precisely what this produces. Reviewing a 150-line change well is easier than reviewing a 2,000-line one badly, and the shorter the branch, the more likely the review happens today rather than tomorrow.
# The pipeline has to make this safe, or the practice is just faster risk.
stages:
- verify: # under 10 minutes, or people batch changes to avoid waiting
- lint, types, affected tests, contract tests
- deploy-staging:
- automatic on merge to main, always
- deploy-production:
- strategy: canary
steps:
- weight: 5
analysis: { metrics: [error_rate, p95_latency], duration: 10m }
- weight: 25
analysis: { metrics: [error_rate, p95_latency], duration: 10m }
- weight: 100
# Automatic rollback on analysis failure. A human is not the mechanism.
rollback_on_failure: trueThe canary analysis is what makes frequent deployment less risky rather than more. Each release contains one small change, so when a metric moves, the cause is unambiguous — which is the opposite of a fortnightly release containing forty changes, where a regression means bisecting through everything that shipped.
- Cap branch age at a day. If work will not fit, split it into steps that each land safely on their own.
- Keep main deployable at every commit. A broken main is an emergency for everyone, and treating it that way is what keeps it rare.
- Invest in the pipeline first. Trunk-based development on a slow, flaky pipeline is genuinely worse than what you had.
- Use expand-migrate-contract for schema changes, so the database never blocks a small change from shipping.
- Delete flags promptly. The practice depends on them, which is exactly why they accumulate.
Integration pain does not go away while you work on a branch. It compounds quietly and arrives all at once, on the day you thought you were finished.
The change that surprises teams is cultural rather than technical. When releases are rare, each one is an event with its own risk and ceremony, so people batch more into it, which makes it riskier. When they happen several times a day, nobody notices them, rollback is routine, and the question 'should this go out today' stops being a discussion. That is the actual deliverable: not deployment frequency as a metric, but a release stopping being a thing anyone has feelings about.