Stage 2 · Tools
Branching Strategies
Trunk-Based Development
Keep branches short-lived, integrate continuously, and ship from main with feature flags.
What Is Trunk-Based Development?
Trunk-based development (TBD) is a branching model where all developers commit to a single branch — typically main or trunk — and long-lived feature branches are eliminated. Feature work is either committed directly in small increments or integrated via extremely short-lived branches that live for less than a day.
This model is used by Google, Meta, Microsoft, and most high-performing engineering organizations. The 2023 DORA State of DevOps report consistently links trunk-based development with higher deployment frequency and lower change failure rates.
Everyone commits to main. If your branch lives longer than one day, you are doing it wrong. Feature flags replace long-lived branches as the mechanism for hiding incomplete work.
Core Principles
Trunk-based development rests on a few non-negotiable practices that make continuous integration work at scale.
- All developers commit to a single branch (main/trunk) at least once per day.
- Feature branches, when used, are short-lived — merged or abandoned within 24 hours.
- Incomplete features are hidden behind feature flags, not branches.
- Every commit triggers an automated build and test pipeline.
- Code review happens before or immediately after merge, not days later.
- Rollback is fast — revert a commit or toggle a flag, not merge a branch back.
Short-Lived Branches
Even in trunk-based development, branches exist — they just do not live long. A developer creates a branch, makes one or two commits, pushes, opens a PR, gets it reviewed, and merges. The entire lifecycle is measured in hours, not days or weeks.
# Start from an up-to-date main
git switch main
git pull origin main
# Create a short-lived branch
git switch -c feat/add-search-filter
# Make your changes, commit small
git add src/search.ts
git commit -m "feat: add search filter component"
git add src/search.test.ts
git commit -m "test: add search filter unit tests"
# Push and open a PR
git push origin feat/add-search-filter
gh pr create --title "Add search filter" --body "Closes #42"
# After review, merge immediately
gh pr merge --squash --delete-branchThe branch lives for minutes to hours. You pull main, branch, commit, push, review, merge. The branch is deleted on merge — it never accumulates days of diverged history.
If your branch exists for more than 24 hours, rebase it on main immediately. The longer a branch lives, the harder the merge. This is the single most important discipline in trunk-based development.
Feature Flags
Feature flags (also called feature toggles) are the mechanism that makes short-lived branches possible. Instead of keeping code on a branch until it is ready, you merge incomplete code to main behind a flag that disables it in production. When the feature is ready, you remove the flag.
// lib/feature-flags.ts
const flags: Record<string, boolean> = {
"new-search": process.env.FF_NEW_SEARCH === "true",
"dark-mode-v2": process.env.FF_DARK_MODE_V2 === "true",
"checkout-redesign": false,
};
export function isEnabled(flag: string): boolean {
return flags[flag] ?? false;
}
// src/components/Header.tsx
import { isEnabled } from "../lib/feature-flags";
export function Header() {
return (
<header>
<h1>My App</h1>
{isEnabled("new-search") ? <SearchBar /> : <LegacySearch />}
</header>
);
}The new search bar is merged to main but hidden behind a flag. You can deploy to production without exposing it. When ready, flip the flag or remove the conditional entirely.
# After the feature is fully rolled out, remove the flag
git switch -c cleanup/remove-search-flag
# Remove the flag from the code
# Delete the conditional, keep only the new path
git add src/components/Header.tsx lib/feature-flags.ts
git commit -m "chore: remove new-search feature flag"
# Remove the flag from environment config
git add .env.production
git commit -m "chore: remove FF_NEW_SEARCH from production"
git push origin cleanup/remove-search-flag
gh pr merge --squash --delete-branchFeature flags are temporary. Clean them up after the feature ships. Dead flag code is technical debt — remove it within a sprint of full rollout.
Benefits and Tradeoffs
| Aspect | Trunk-Based Development | Long-Lived Feature Branches |
|---|---|---|
| Integration frequency | Multiple times per day | Days or weeks |
| Merge conflict risk | Low — small, frequent integrations | High — large, infrequent merges |
| Code review speed | Fast — small PRs | Slow — massive diffs |
| Release cadence | Continuous — deploy from main | Batched — release from release branch |
| Feature isolation | Feature flags | Branches |
| CI feedback loop | Minutes | Hours or days |
| Rollback simplicity | Revert commit or toggle flag | Cherry-pick or re-merge |
When to Use It
Trunk-based development works best for teams practicing continuous deployment with automated test suites and feature flag infrastructure. It is the default model for SaaS products, web applications, and API services.
If you release boxed software with versioned installers, ship mobile apps that undergo App Store review, or lack automated testing infrastructure, trunk-based development may be premature. GitFlow or GitHub Flow may be more practical starting points.
- You have a comprehensive automated test suite (unit, integration, e2e).
- You deploy to production multiple times per day.
- You have feature flag infrastructure (even a simple environment variable system).
- Your team is small enough that coordination overhead is low.
- You practice continuous integration with fast build pipelines.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.