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.

published: reading time: 13 min read updated: March 31, 2026

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:

PillarDescription
Single trunkOne shared branch (usually main) that is always releasable
Short-lived branchesFeature branches last hours, not days. Maximum 1-2 days.
Feature flagsIncomplete features are hidden behind runtime toggles, not branches
Continuous integrationAutomated 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 + Mitigations

ScenarioWhat HappensMitigation
Trunk breakA bad commit breaks the build for everyoneCI blocks the merge; if it slips through, revert immediately; blameless postmortem
Feature flag leakA flagged feature is accidentally exposed to usersAutomated tests verify flag states; staging environment with production flag config
Merge conflict stormMultiple developers modify the same files simultaneouslyCommunicate in standup; use CODEOWNERS for sensitive areas; rebase frequently
Flag debt accumulationOld feature flags clutter the codebaseSchedule flag cleanup sprints; set expiration dates on flags; automate flag detection
Partial feature deploymentHalf a feature ships because the second PR isn’t readyDesign features to be incrementally valuable; use flags to hide incomplete pieces

Trade-offs

AspectAdvantageDisadvantage
Integration costNear-zero — branches merge dailyRequires discipline to keep branches short
Deployment frequencyMultiple times per dayRequires robust CI/CD and monitoring
Code qualityContinuous review catches issues earlyRequires experienced developers
Feature managementFlags enable progressive rolloutFlag management adds complexity
Team coordinationEveryone works on the same versionConflicts are resolved immediately, not deferred
ScalabilityWorks for thousands of developersRequires 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 and Anti-Patterns

  1. 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.
  2. Flag Sprawl — Hundreds of stale feature flags make the codebase unreadable. Set expiration dates and clean up regularly.
  3. Skipping Tests — Without fast, reliable tests, trunk breaks become daily events. Invest in CI infrastructure first.
  4. Big Bang Merges — Merging a week’s worth of work in one PR defeats the purpose. Break work into daily mergeable chunks.
  5. Flag Coupling — Features that depend on each other’s flags create complex toggle combinations. Design features to be independently flaggable.
  6. No Rollback Plan — When a trunk merge breaks production, you need instant rollback. Automated rollback on error rate spikes is essential.
  7. 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 Q&A

How does Trunk-Based Development differ from GitHub Flow?

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.

What happens when a developer breaks the trunk?

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.

How do you manage feature flags at scale?

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
Why do Google and Facebook use Trunk-Based Development?

At their scale, integration cost is the dominant bottleneck. With tens of thousands of developers, if everyone maintained long-lived branches, the merge conflicts would be unmanageable. Trunk-based development forces integration to happen continuously, keeping the cost per integration near zero.

They also have the infrastructure to support it: CI systems that can run millions of tests per day, feature flag platforms serving billions of evaluations, and monitoring systems that detect production issues within seconds.

Resources

Category

Related Posts

Choosing a Git Team Workflow: Decision Framework for Branching Strategies

Decision framework for selecting the right Git branching strategy based on team size, release cadence, project type, and organizational maturity. Compare Git Flow, GitHub Flow, and more.

#git #version-control #branching-strategy

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 #version-control #branching-strategy

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.

#git #version-control #release-management