Trunk-Based Development: The Branching Strategy Behind Google and Facebook
Explore Trunk-Based Development — the branching model used by Google, Meta, and Netflix. Learn about short-lived branches, feature flags, and continuous integration at scale.
Trunk-Based Development: The Branching Strategy Behind Google and Facebook
Trunk-Based Development is the branching strategy that powers the largest software organizations on Earth. Google, Meta, Netflix, and Amazon all use variations of this model. It is the simplest branching strategy in existence and the hardest to adopt.
The core idea: everyone commits to a single shared branch (the trunk) at least daily. Feature branches, if they exist, last hours — not days. Code that isn’t ready for users is hidden behind feature flags, not isolated in long-lived branches.
This post explains why the world’s most productive engineering teams use this approach, what it takes to make it work, and why most teams fail when they try.
When to Use / When Not to Use
Use Trunk-Based Development When
- Large engineering organizations — 50+ developers working on the same codebase
- Continuous deployment — Multiple deployments per day to production
- Strong CI/CD infrastructure — Automated testing that runs in minutes, not hours
- Feature flag platform — Infrastructure to toggle features on and off at runtime
- Experienced teams — Developers comfortable with small, incremental commits
Do Not Use Trunk-Based Development When
- Small teams without CI — Without automated testing, trunk breaks are frequent and costly
- Scheduled release cycles — If you ship monthly, the trunk model provides no structure
- Junior-heavy teams — Requires discipline around small commits and code review
- No feature flag infrastructure — Without flags, incomplete features block the trunk
- Regulated environments — Formal change management processes conflict with continuous commits
Core Concepts
Trunk-Based Development is built on four pillars:
| Pillar | Description |
|---|---|
| Single trunk | One shared branch (usually main) that is always releasable |
| Short-lived branches | Feature branches last hours, not days. Maximum 1-2 days. |
| Feature flags | Incomplete features are hidden behind runtime toggles, not branches |
| Continuous integration | Automated tests run on every commit, blocking broken code from trunk |
The model assumes that the cost of integrating code increases exponentially with branch age. A branch that’s one day old has trivial merge conflicts. A branch that’s two weeks old has conflicts in every file.
graph LR
A[main - Trunk] --> B[short feature 1]
A --> C[short feature 2]
A --> D[short feature 3]
B -->|hours| A
C -->|hours| A
D -->|hours| A
A --> E[Continuous Deploy]
Architecture and Flow Diagram
The complete Trunk-Based Development workflow with feature flags and CI gates:
graph TD
A[main - Always Releasable] -->|fork| B[feature branch]
B -->|commit| B
B -->|push + CI| C{CI Pass?}
C -->|No| D[Fix and retry]
D --> B
C -->|Yes| E[Code Review]
E -->|Approved| F[Squash merge to main]
F --> G[Deploy to Production]
G -->|Feature Flag OFF| H[Code deployed but hidden]
G -->|Feature Flag ON| I[Feature visible to users]
Step-by-Step Guide
1. Commit to Trunk Frequently
The golden rule: no developer should go more than one day without merging to trunk. In practice, top teams merge 3-10 times per day.
# Start with latest trunk
git checkout main
git pull origin main
# Create a short-lived branch
git checkout -b feat/add-search-filter
# Make small, focused changes
git add src/components/SearchFilter.tsx
git commit -m "feat: add search filter component skeleton"
# Push and open PR immediately
git push -u origin feat/add-search-filter
2. Use Feature Flags for Incomplete Work
Never let incomplete code block your merge. Hide it behind a flag:
// src/pages/SearchPage.tsx
import { useFeatureFlag } from '@/hooks/useFeatureFlag';
export function SearchPage() {
const showAdvancedFilters = useFeatureFlag('advanced-search-filters');
return (
<div>
<SearchInput />
{showAdvancedFilters && <AdvancedFilters />}
<SearchResults />
</div>
);
}
// Feature flag configuration
// Can be toggled without code deployment
const FEATURE_FLAGS = {
'advanced-search-filters': false, // Toggle to true when ready
'new-search-algorithm': true, // Already rolled out
};
3. Keep Branches Short-Lived
Break large features into small, mergeable increments:
# Instead of one massive branch for "search feature":
# Day 1: Search input component
git checkout -b feat/search-input
# ... implement, test, merge
# Day 1: Search results list
git checkout -b feat/search-results
# ... implement, test, merge
# Day 2: Search filters (behind flag)
git checkout -b feat/search-filters
# ... implement behind flag, test, merge
# Day 2: Search analytics
git checkout -b feat/search-analytics
# ... implement, test, merge
Each increment is independently mergeable and testable.
4. Code Review on Every Change
Code review is the primary quality gate in trunk-based development:
# Open PR with small, focused changes
gh pr create \
--title "feat: add search input component" \
--body "Part 1 of search feature. Adds the search input UI component.
Advanced filters and results come in follow-up PRs."
# Review should be fast — small PRs get reviewed quickly
# Target: review within 1 hour, merge within 4 hours
5. Automated Testing on Every Commit
Your CI pipeline must be fast enough to not block the flow:
# .github/workflows/trunk-ci.yml
name: Trunk CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
fast-checks:
# These run in parallel, target: < 5 minutes
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test -- --testPathPattern=unit
integration-tests:
needs: fast-checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run test:integration
deploy:
needs: integration-tests
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./scripts/deploy.sh
Production Failure Scenarios
| Scenario | What Happens | Mitigation |
|---|---|---|
| Trunk break | A bad commit breaks the build for everyone | CI blocks the merge; if it slips through, revert immediately; blameless postmortem |
| Feature flag leak | A flagged feature is accidentally exposed to users | Automated tests verify flag states; staging environment with production flag config |
| Merge conflict storm | Multiple developers modify the same files simultaneously | Communicate in standup; use CODEOWNERS for sensitive areas; rebase frequently |
| Flag debt accumulation | Old feature flags clutter the codebase | Schedule flag cleanup sprints; set expiration dates on flags; automate flag detection |
| Partial feature deployment | Half a feature ships because the second PR isn’t ready | Design features to be incrementally valuable; use flags to hide incomplete pieces |
Trade-off Analysis
| Aspect | Advantage | Disadvantage |
|---|---|---|
| Integration cost | Near-zero — branches merge daily | Requires discipline to keep branches short |
| Deployment frequency | Multiple times per day | Requires robust CI/CD and monitoring |
| Code quality | Continuous review catches issues early | Requires experienced developers |
| Feature management | Flags enable progressive rollout | Flag management adds complexity |
| Team coordination | Everyone works on the same version | Conflicts are resolved immediately, not deferred |
| Scalability | Works for thousands of developers | Requires significant infrastructure investment |
Implementation Snippets
Feature Flag SDK Pattern
// src/lib/featureFlags.ts
interface FeatureFlagConfig {
name: string;
enabled: boolean;
rolloutPercentage?: number;
expirationDate?: string;
}
class FeatureFlagManager {
private flags: Map<string, FeatureFlagConfig> = new Map();
async load(): Promise<void> {
const response = await fetch("/api/feature-flags");
const flags: FeatureFlagConfig[] = await response.json();
flags.forEach((f) => this.flags.set(f.name, f));
}
isEnabled(name: string, userId?: string): boolean {
const flag = this.flags.get(name);
if (!flag) return false;
if (!flag.enabled) return false;
if (flag.rolloutPercentage !== undefined) {
const hash = this.hashUserId(userId || "anonymous");
return hash % 100 < flag.rolloutPercentage;
}
return true;
}
// Check for expired flags
getExpiredFlags(): FeatureFlagConfig[] {
const now = new Date();
return Array.from(this.flags.values()).filter(
(f) => f.expirationDate && new Date(f.expirationDate) < now,
);
}
private hashUserId(id: string): number {
let hash = 0;
for (let i = 0; i < id.length; i++) {
hash = (hash << 5) - hash + id.charCodeAt(i);
hash |= 0;
}
return Math.abs(hash);
}
}
export const featureFlags = new FeatureFlagManager();
Pre-commit Hook — Enforce Small Commits
#!/bin/bash
# .git/hooks/pre-commit
# Warn if commit message doesn't follow convention
MSG_FILE=$1
MSG=$(cat "$MSG_FILE")
# Check for conventional commit prefix
if ! echo "$MSG" | grep -qE '^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?:'; then
echo "WARNING: Commit message should follow conventional commit format."
echo "Example: feat: add search filter component"
echo "Continue anyway? (y/n)"
read -r answer
if [ "$answer" != "y" ]; then
exit 1
fi
fi
Branch Age Monitor
#!/bin/bash
# scripts/check-branch-age.sh
# Alert when feature branches exceed 2 days
MAX_AGE_DAYS=2
THRESHOLD=$(date -d "$MAX_AGE_DAYS days ago" +%s 2>/dev/null || date -v-"${MAX_AGE_DAYS}d" +%s)
git branch -r --format='%(refname:short) %(committerdate:unix)' | \
grep 'origin/feature/' | \
while read -r branch timestamp; do
if [ "$timestamp" -lt "$THRESHOLD" ]; then
echo "WARNING: $branch is older than $MAX_AGE_DAYS days"
fi
done
Observability Checklist
- Logs: Log every feature flag evaluation in production for audit trails
- Metrics: Track branch age distribution, merge frequency per developer, and CI pipeline duration
- Traces: Trace each deployment back to the specific commits and PRs that triggered it
- Alerts: Alert when branch age exceeds 2 days, CI pipeline exceeds 15 minutes, or trunk break rate exceeds 1 per week
- Dashboards: Display trunk health metrics: build success rate, average merge time, flag count, and deployment frequency
Security and Compliance: Trunk-Based Development
- Feature flag governance: Treat production feature flags as deployment controls. Only authorized personnel should toggle flags in production.
- Flag access control: Implement role-based access for flag management. Developers can create flags in staging; only release managers toggle production flags.
- Flag audit trail: Log every flag change with timestamp, user identity, and business justification. This is critical for SOC 2 and HIPAA compliance.
- Flag expiration enforcement: CI should fail builds that contain flags past their expiration date. Stale flags are a security risk — they expand the attack surface.
- Code review enforcement: Use branch protection to require reviews before any merge to trunk. Automated CI pipelines serve as change approval records.
- Compliance mapping: Each trunk merge is a change event. Map merge commits to change tickets for audit purposes.
- Secret management: Never store flag configuration or toggle values in code. Use a secure flag management service with encryption at rest.
Common Pitfalls / Anti-Patterns
- The “Trunk” That Isn’t — Teams that call it trunk-based but allow week-long branches are just doing Git Flow with different names. Enforce branch age limits.
- Flag Sprawl — Hundreds of stale feature flags make the codebase unreadable. Set expiration dates and clean up regularly.
- Skipping Tests — Without fast, reliable tests, trunk breaks become daily events. Invest in CI infrastructure first.
- Big Bang Merges — Merging a week’s worth of work in one PR defeats the purpose. Break work into daily mergeable chunks.
- Flag Coupling — Features that depend on each other’s flags create complex toggle combinations. Design features to be independently flaggable.
- No Rollback Plan — When a trunk merge breaks production, you need instant rollback. Automated rollback on error rate spikes is essential.
- Ignoring Code Review — Fast merges without review lead to quality degradation. Small PRs enable fast review — don’t skip it.
Quick Recap Checklist
- Everyone commits to trunk at least once per day
- Feature branches exist for hours, not days
- Incomplete features are hidden behind feature flags
- CI pipeline runs on every commit and blocks broken code
- Code review is required for every merge to trunk
- Feature flags have expiration dates and are cleaned up regularly
- Trunk is always in a deployable state
- Failed deployments trigger automatic rollback
- Branch age is monitored and alerts fire for old branches
- Feature flag changes are logged and audited
Interview Questions
Both use a single main branch, but the key difference is branch lifetime. GitHub Flow allows feature branches to exist for days or weeks while Trunk-Based Development enforces branches lasting hours to a maximum of 1-2 days.
Trunk-Based Development also relies more heavily on feature flags to manage incomplete work, whereas GitHub Flow may leave features in branches until they're complete. At scale, trunk-based requires more infrastructure investment but delivers higher integration velocity.
The immediate response is revert the commit — not fix forward. Getting the trunk green again is the highest priority. Then conduct a blameless postmortem to understand why the CI pipeline didn't catch the issue.
Prevention is better than cure: fast CI pipelines that run on every push, required code review, and small commits all reduce the probability of trunk breaks. Google reports that their trunk break rate is less than 1% of all commits.
Use a dedicated feature flag platform like LaunchDarkly, Split, or an internal solution. Key practices include:
- Expiration dates — Every flag has a planned removal date
- Automated cleanup — CI checks for flags past their expiration
- Rollout percentages — Gradually increase exposure from 1% to 100%
- Access control — Only authorized personnel can toggle production flags
- Audit logging — Every flag change is recorded with who, when, and why
In GitFlow, developers work on feature branches for days or weeks before merging, resulting in integration happening infrequently. Trunk-based development mandates daily integration, with top teams merging 3-10 times per day. At scale, this near-continuous integration keeps merge conflicts small and manageable, while GitFlow's infrequent integration creates complex, multi-file conflicts that take days to resolve. Google's internal data shows their integration velocity is 30-50x higher than teams using long-lived branching strategies.
Long-running features are decomposed into vertical slices that can be completed and merged within hours. Each slice should deliver some user value independently, even if hidden behind a feature flag. For example, a search feature might be broken into: (1) basic search input UI, (2) search results display, (3) filter controls, (4) search analytics. Each piece merges to trunk on its own day, with flags hiding incomplete functionality. This requires significant upfront design work but eliminates the integration debt that accumulates with long branches.
The minimum viable infrastructure includes: (1) Fast unit tests completing in under 5 minutes, (2) Automated build verification on every push, (3) Branch protection blocking merges when CI fails, (4) Feature flag platform for hiding incomplete work. Without these, trunk breaks become daily events. The team spends more time fixing integration issues than building features. Netflix and Google invested millions in CI infrastructure before adopting trunk-based development — the model only works when CI is faster than manual testing would be.
Feature flags add operational complexity through: flag proliferation (hundreds of flags become unreadable), flag dependency chains (features requiring multiple flags create toggle matrix problems), and stale flags that never get cleaned up. Management practices include: mandatory expiration dates on every flag enforced by CI, automated flag detection and alerting, quarterly flag cleanup sprints, and treating flags as production infrastructure rather than code. LaunchDarkly reports that teams with mature flag practices spend 15% less time on release management.
Key metrics include: Integration frequency (commits to trunk per day, target: 5+), Branch age (average hours from branch creation to merge, target: under 24), Trunk break rate (build failures per week, target: under 1), Cycle time (commit to production, target: under 1 hour), Flag count (total active flags, tracked for cleanup), Rollback frequency (emergency reversions per month). DORA research shows elite performers achieve: less than 1 hour cycle time, less than 1% trunk break rate, and multiple daily deployments.
Trunk-based development works well with microservices when each service independently practices trunk-based development. However, conflicts arise when services have different release cadences — Service A may be on trunk while Service B is weeks behind. This creates version skew at runtime. Solutions include: (1) contract testing between services, (2) shared trunk for tightly coupled services, (3) API versioning strategies. Amazon's "two-pizza team" model assigns each team full ownership of their services, allowing independent trunk-based adoption per service while maintaining compatibility through API contracts.
Frequent small commits spread risk across time — each commit has a smaller blast radius if it breaks trunk. A bug in one day's worth of commits affects a small percentage of the codebase. In contrast, a week's worth of work in one merge creates a large blast radius that's harder to diagnose. However, small commits require discipline: each must be coherent and testable in isolation. The CI pipeline becomes the safety net — if tests pass, the commit is safe. The trade-off is investing in fast, reliable tests that don't create false positives.
The feature flag architecture enables safe hotfixes: completed code is already on trunk and deployed, while incomplete code is hidden behind flags. For hotfixes, the developer creates a short branch from the current trunk, fixes the bug, merges through CI, and deploys. The incomplete features remain hidden by their flags and do not affect the fix. If a hotfix requires changes to flagged code, it can be applied directly — flags hide functionality, not code structure. The hotfix deploys, and users see the fix regardless of what flags are on or off.
Trunk-based development requires shifting accountability to the individual developer — when you merge broken code, you block everyone immediately. Cultural changes include: (1) Blame-free postmortems when trunk breaks, focusing on system improvements, (2) Developer-owned quality — no separate QA phase to catch what CI missed, (3) Small PR culture — code review must happen in hours, not days, (4) Feature flag discipline — treating flags as first-class infrastructure. Teams transitioning from GitFlow typically see 2-3 months of lower productivity before velocity increases as integration costs disappear.
Feature flag debt accumulates when flags outlive their purpose. Prevention strategies: (1) Expiration dates on every flag — CI fails if a flag expires without removal, (2) Flag ownership — each flag has an owner responsible for removal, (3) Quarterly flag audits — dedicated sprint time for flag cleanup, (4) Automated flag detection — scripts flag code that references deleted flags, (5) Flag count dashboards — visibility into flag growth trends. Google's internal guidelines require all flags to have expiration dates, with CI blocking merges for expired flags.
Continuous deployment creates compliance challenges: (1) Change management — regulators may require documented approval before production changes, (2) Audit trails — every trunk merge must be traceable to a change request, (3) Segregation of duties — developers write code, but who approves deployment?, (4) Secret management — flag configurations should not contain credentials. Solutions include treating the CI pipeline as the approval record, implementing mandatory code review as change approval, and integrating flag management platforms with compliance tools. SOC 2 Type II audits require demonstrable controls over production changes.
Database migrations in trunk-based development follow backward-compatible patterns: (1) Add new columns as nullable or with defaults before using them, (2) Deploy schema change to all instances before activating flag-controlled features, (3) Use read-your-writes consistency to handle cases where the new code runs before the migration completes. For risky migrations, the pattern is: migrate schema in one trunk commit, deploy, then add the code using the new schema in a subsequent commit behind a flag. This ensures the database is always compatible with whatever code is currently deployed.
Technical debt in trunk-based development is managed through: (1) Strangler fig patterns — new code gradually replaces old code behind flags, allowing incremental migration, (2) Debt budgets — allocate 20% of sprint capacity to debt reduction, tracked like any other work, (3) Automated refactoring tools — linters and codemods handle mechanical transformations, (4) Dedicated debt sprints — periodic focused efforts on specific debt areas. The key constraint: debt work must be mergeable in small increments, not sprawling refactors that take weeks. If a refactor cannot be broken down, it is a candidate for the strangler fig pattern.
Effective code review in trunk-based development requires: (1) Small PRs under 400 lines — reviewers can complete reviews in under 30 minutes, (2) Async-first review culture — non-blocking comments distinguish blocking ("must fix") from non-blocking ("nit:"), (3) Review SLAs — target 1-hour response time, enforced by team norms, (4) Automated pre-review checks — linting, formatting, and tests run before human review, (5) Reviewer rotation — distribute knowledge and prevent bottlenecks. Google's research shows that PRs under 400 lines have a 78% first-review approval rate within 24 hours, compared to 35% for PRs over 1000 lines.
Feature flag validation strategies include: (1) Staging environment with production-equivalent flag config — test flag combinations before production release, (2) Canary deployments — enable flags for 1% of users first, then gradually increase, (3) Automated flag testing — integration tests verify flag-gated code paths are exercised with both flag states, (4) Flag audit logs — every flag change triggers review in staging before production, (5) Progressive rollout percentages — start with internal users, then beta, then gradual public rollout. LaunchDarkly's SDKs include kill switch capabilities for instant flag disable if issues arise.
Automated rollback in trunk-based development triggers when: (1) Error rate spikes — predefined thresholds on specific metrics (e.g., 5% increase in 5xx errors), (2) Latency degradation — p99 latency exceeds baseline by 50%, (3) Health check failures — continuous /health endpoint failures. Rollback is automatic when the trigger is unambiguous. Manual intervention is required when: the root cause is unclear (need to investigate before rolling back wrong thing), flags are involved (need to determine if flag or code caused issue), or business metrics are degraded without clear technical cause (e.g., conversion rate drops). The goal is to recover to known good state within 5 minutes of detecting an issue.
Trunk-based development requires significantly more tooling investment: (1) CI/CD pipeline must be fast (< 10 min) and reliable — Git Flow can tolerate slower pipelines because integration is less frequent, (2) Feature flag platform is mandatory — Git Flow doesn't need flags, (3) Rollback automation is essential — manual rollback is too slow for multiple daily deploys, (4) Environment parity — staging must match production flag states. Git Flow's deployment model is simpler: merge to release branch, test, deploy. At scale, however, Git Flow's integration complexity far exceeds the tooling complexity of trunk-based development.
Further Reading
- Trunk-Based Development official site — Comprehensive reference with branching patterns
- Google’s engineering practices — Software Engineering at Google book
- Feature Flags best practices — LaunchDarkly’s guide
- DORA State of DevOps Report — Annual report on software delivery performance
- Martin Fowler on Trunk-Based Development — Detailed analysis of branching patterns
Conclusion
Trunk-based development demands discipline — short-lived branches, feature flags, and continuous integration. The payoff is minimal merge pain, fast feedback, and the ability to deploy any time. It’s the strategy that scales to hundreds of developers.
Category
Tags
Related Posts
Choosing a Git Team Workflow: Decision Framework
Decision framework for selecting the right Git branching strategy based on team size, release cadence, and project type.
Git Flow: The Original Branching Strategy Explained
Master the Git Flow branching model with master, develop, feature, release, and hotfix branches. Learn when to use it, common pitfalls, and production best practices.
Git Release Branching and Hotfixes: Managing Versions in Production
Master release branching and hotfix strategies in Git. Learn version branches, emergency fixes, backporting, and how to manage multiple production versions safely.