What Is Trunk Based Development?
Trunk-based development is a source-control branching model where developers collaborate on code in a single branch called the "trunk" (or "main" in Git terminology), resisting any pressure to create other long-lived development branches. This approach has been practiced since the mid-1990s and is used at massive scale by companies like Google, which operates with over 35,000 developers in a single monorepo using this methodology TrunkBasedDevelopment.com.
The fundamental philosophy behind trunk-based development is elegant in its simplicity: instead of creating separate branches that diverge from the main codebase for extended periods, developers integrate their changes directly and frequently into the trunk. This practice eliminates the merge conflicts that plague long-lived feature branches and ensures that the codebase always remains in a releasable state.
At its core, trunk-based development embraces two key principles:
- Single source of truth: The trunk serves as the definitive, authoritative version of the codebase that all developers work from and contribute to
- Frequent commits and integration: Developers make commits multiple times per day, ensuring their work remains synchronized with the rest of the team
The term "trunk" itself is referent to the concept of a growing tree, where the fattest and longest span is the trunk, not the branches that radiate from it and are of more limited length. This metaphor elegantly captures the essence of the approach: the trunk is the stable, permanent foundation, while short-lived branches are merely temporary offshoots that quickly rejoin the main stem.
The Evolution of Development Practices
Trunk-based development evolved as a response to the challenges that teams faced with traditional branching models, particularly Gitflow and other approaches that relied on multiple long-lived branches. Over the past three decades, advances in source-control technologies and related tools have made trunk-based development increasingly practical and attractive.
In the early days of software development, branching was often a cumbersome and error-prone process. Tools like ClearCase, Subversion, and PerForce had limited branching capabilities, and creating branches typically meant copying entire repositories, which was slow and storage-intensive. Modern version control systems, particularly Git, have fundamentally changed this landscape by making branching fast, cheap, and almost frictionless. However, this ease of branching has paradoxically created new challenges: when creating branches is trivial, teams tend to create more of them, often leading to complex branching strategies that introduce their own set of problems.
The DevOps movement and the rise of continuous delivery have further accelerated the adoption of trunk-based development. Practices like continuous integration (CI), continuous delivery (CD), and continuous deployment rely on the ability to integrate code changes quickly and reliably--capabilities that trunk-based development inherently provides. As organizations have embraced these practices, they have increasingly recognized that trunk-based development is not just a viable option but often the optimal choice for teams seeking to deliver software rapidly and reliably.
For teams looking to modernize their software development practices, trunk-based development provides a foundation that enables faster iteration cycles and higher quality code delivery.
Core Principles of Trunk Based Development
The Single Source of Truth
The most fundamental principle of trunk-based development is the concept of a single source of truth. In this model, the trunk serves as the definitive, authoritative version of the codebase that all developers work from and contribute to. There is no "develop" branch, no "integration" branch, and no separate "mainline" that exists independently from where developers commit their daily work.
This approach differs significantly from other branching models. In Gitflow, for example, there are two main branches with infinite lifetime: master and develop. Features are developed on separate branches and merged into develop when ready, while releases are managed through dedicated release branches. This separation, while providing certain organizational benefits, also introduces complexity and potential integration challenges that trunk-based development aims to eliminate.
When everyone works from and contributes to the same trunk, several benefits emerge. First, there is complete transparency about the state of the codebase--anyone can look at the trunk and see exactly what changes have been made and by whom. Second, integration issues become immediately apparent because developers are constantly merging their work with everyone else's. Third, the cognitive load on developers is reduced because they do not need to track multiple branches and understand how they relate to each other.
Frequent Commits and Continuous Integration
The second core principle of trunk-based development is the practice of making frequent commits and continuously integrating changes into the trunk. Developers are encouraged to commit their code changes multiple times per day, rather than working in isolation on a feature branch for days or weeks before merging.
This practice of continuous integration (CI) is crucial to the success of trunk-based development. Each commit to the trunk triggers automated build and test processes that verify the code change does not break any existing functionality. By integrating frequently and validating each change through automated testing, teams can detect and resolve integration issues immediately, before they have a chance to accumulate and become difficult to debug.
The key requirement of continuous integration is that all team members commit to the trunk at least once every 24 hours. This ensures that the trunk always represents the most recent state of the codebase and that no developer becomes significantly out of sync with the rest of the team. While very small teams may even commit directly to the trunk without using feature branches at all, larger teams typically use short-lived feature branches that are merged back into the trunk within a day or two.
The discipline of frequent integration also has psychological benefits. When developers know they will be merging their work into a shared codebase multiple times per day, they tend to break their work into smaller, more manageable pieces. This leads to better designed code and more thoughtful implementation decisions, as developers must consider how their changes will integrate with the work of others throughout the day rather than in a single massive merge at the end of a feature development cycle.
Short-Lived Feature Branches
While trunk-based development emphasizes working directly on the trunk, it does not preclude the use of branches entirely. However, the branches that are used in trunk-based development are fundamentally different from those in traditional branching models. In trunk-based development, branches are short-lived--they are created for a specific purpose, used briefly, and then merged back into the trunk and deleted.
The typical lifespan of a feature branch in trunk-based development is one to two days, although some organizations may allow branches to live for up to a week before merging. The key principle is that branches should be short enough that they do not diverge significantly from the trunk, which would create the merge conflicts that trunk-based development aims to eliminate.
Short-lived branches serve several purposes. They provide a space for developers to work on a specific feature or fix without interfering with the work of others. They enable code review workflows, allowing team members to examine changes before they are merged into the trunk. And they facilitate testing, as changes can be validated in a branch before affecting the main codebase. However, because these branches are short-lived, they do not have the opportunity to drift far from the trunk, making integration straightforward and predictable.
Implementing these practices requires a robust CI/CD pipeline that can validate changes quickly and reliably, providing the feedback loop that makes trunk-based development effective.
Why leading engineering teams are adopting this approach
Faster Release Cycles
Because the trunk is always in a releasable state, teams are not blocked by long-running feature branches or complex integration work when it comes time to release.
Improved Code Quality
The requirement to integrate frequently means that developers cannot accumulate large amounts of unchecked code--each change must be well-formed and well-tested.
Reduced Merge Conflicts
Short-lived branches and frequent integration prevent branches from diverging significantly, making merge conflicts rare and easy to resolve.
Increased Productivity
Developers spend less time dealing with integration issues and more time building valuable features with constant feedback from CI pipelines.
How Trunk Based Development Works
The Daily Workflow
A typical day for a developer practicing trunk-based development begins with updating their local workspace from the trunk. Because everyone is working from the same baseline, this update process is straightforward--there are no complex merges from multiple feature branches to resolve, and no significant divergences to reconcile. The developer pulls the latest changes from the trunk and begins their work.
As the developer works, they make small, focused commits directly to their local trunk or to a short-lived feature branch. Each commit represents a logical unit of work--a bug fix, a small feature increment, or a refactoring change. These commits are pushed to the shared repository frequently, typically multiple times per day, ensuring that the developer's work is visible to the team and integrated with others' changes.
When the developer pushes their changes, an automated CI pipeline springs into action. The pipeline builds the code, runs automated tests, and performs other quality checks. If everything passes, the changes are integrated into the shared trunk. If there are issues, the developer is immediately notified and can address them before the problem compounds or affects other team members.
Continuous Integration and Testing
Continuous integration is the engine that makes trunk-based development possible. Without robust CI, the practice of frequent commits to a shared trunk would be risky--any broken code committed to the trunk would immediately affect all other developers. With CI, however, each commit is validated before and after it is merged, providing confidence that the trunk remains in a healthy state.
A typical CI pipeline in a trunk-based development environment includes several stages:
# Example CI Pipeline Configuration
stages:
- build
- test
- quality
- deploy
build:
stage: build
script:
- npm run build
- echo "Build completed successfully"
unit-tests:
stage: test
script:
- npm run test:unit
artifacts:
reports:
junit: junit.xml
integration-tests:
stage: test
script:
- npm run test:integration
linting:
stage: quality
script:
- npm run lint
- npm run type-check
Each commit triggers this pipeline, providing rapid feedback on the quality and correctness of changes. The speed of the CI pipeline is crucial--if it takes too long to run, developers will be tempted to batch their commits, which defeats the purpose of trunk-based development.
Releasing from the Trunk
One of the most powerful aspects of trunk-based development is the ability to release directly from the trunk. Because the trunk is always in a releasable state--it has been validated by the CI pipeline and contains only small, well-tested changes--teams can choose to deploy to production at any time.
This capability enables several valuable practices. First, teams can adopt continuous deployment, automatically deploying every change that passes the CI pipeline to production. Second, teams can practice progressive delivery, releasing changes to a subset of users initially and gradually expanding the rollout. Third, teams can respond rapidly to market changes or customer feedback, deploying updates within hours or days rather than weeks or months.
Releasing from the trunk does not necessarily mean deploying every change immediately to all users. Many organizations use feature flags to control which features are visible to which users. This allows code for new features to be merged into the trunk and deployed to production, while the features themselves remain hidden until the team is ready to release them.
Implementing a complete CI/CD pipeline requires expertise in DevOps practices and infrastructure management to ensure reliable, automated delivery of your software.
| Aspect | Trunk Based Development | Gitflow |
|---|---|---|
| Branch Strategy | Single trunk with short-lived branches | Multiple long-lived branches (master, develop, feature, release, hotfix) |
| Integration Frequency | Multiple times per day | Once per feature completion |
| Release Process | Direct from trunk | From release branches |
| Merge Complexity | Low - small, frequent merges | High - large, infrequent merges |
| Best For | Continuous delivery, rapid iteration | Formal release processes, regulated industries |
Key Techniques for Success
Feature Flags and Toggles
Feature flags (also known as feature toggles) are a crucial technique for teams practicing trunk-based development. They allow developers to merge code for incomplete features into the trunk without exposing those features to all users. By wrapping new functionality in conditional logic that can be toggled on or off, teams can deploy code to production while keeping new features hidden until they are ready for release.
// Feature Flag Implementation Example
const featureFlags = {
newCheckoutFlow: {
enabled: true,
rolloutPercentage: 25,
userSegments: ['beta-testers']
},
darkMode: {
enabled: true,
rolloutPercentage: 100,
userSegments: ['all']
}
};
function isFeatureEnabled(featureName, user) {
const flag = featureFlags[featureName];
if (!flag || !flag.enabled) return false;
// Check user segment eligibility
if (flag.userSegments && !flag.userSegments.includes('all')) {
if (!user || !flag.userSegments.includes(user.segment)) {
return false;
}
}
// Check percentage rollout
if (flag.rolloutPercentage < 100) {
const userId = user ? hashUserId(user.id) : 0;
return (userId % 100) < flag.rolloutPercentage;
}
return true;
}
// Usage in component
function CheckoutPage() {
const useNewFlow = isFeatureEnabled('newCheckoutFlow', currentUser);
return useNewFlow ? <NewCheckout /> : <LegacyCheckout />;
}
Feature flags provide several benefits in a trunk-based development context. First, they allow for continuous deployment even when features are incomplete. Second, they enable progressive rollouts, where features can be released to a subset of users initially and expanded gradually. Third, they provide a safety net--if a problem is discovered with a new feature, it can be turned off instantly without requiring a code rollback.
Managing feature flags effectively requires discipline and good practices. Flags should have clear naming conventions and documentation so that developers understand what each flag controls. Flags should be removed (or "retired") once the feature is fully released and the toggle code is no longer needed.
Branch by Abstraction
Branch by abstraction is a technique for making large-scale changes to a codebase without using a long-lived branch. Instead of creating a branch to work on a significant refactoring or architectural change, developers use abstraction to create a layer that can be implemented in multiple ways, allowing the old and new implementations to coexist temporarily.
The technique works by identifying an abstraction that covers the area of code being changed. This might be an interface, an abstract class, or simply a well-defined API. The existing implementation is wrapped behind this abstraction. Then, a new implementation is created that also satisfies the abstraction. The codebase is gradually migrated to use the new implementation, with the ability to switch between old and new implementations controlled through configuration or feature flags.
Continuous Code Review
Code review is an essential practice in trunk-based development, providing a human check on the automated checks performed by the CI pipeline. Even with comprehensive automated testing, there are aspects of code quality that are best evaluated by human reviewers: architectural decisions, readability and maintainability, adherence to team conventions, and potential edge cases that might not be covered by tests.
Effective code review practices in trunk-based development include:
- Keep reviews small and focused: Small PRs are easier to review quickly and thoroughly, encouraging faster turnaround
- Respond to review requests quickly: Establish team norms around response times to maintain flow
- Focus on substantive issues: Use linters and formatters to automate style checking so human reviewers focus on architecture and logic
- Be constructive: Code review should be collaborative, not critical--aim to improve code and mentor developers
Many teams use pull request workflows to facilitate code review. Even in trunk-based development, where branches are short-lived, the PR process provides a valuable checkpoint where changes can be reviewed before being merged into the trunk. The key is to keep the PR process efficient--large PRs that take a long time to review encourage developers to batch their work, which undermines frequent integration.
Building these practices into your workflow requires both cultural change and the right software development infrastructure.
Frequently Asked Questions
Sources
- TrunkBasedDevelopment.com - The authoritative source on trunk-based development maintained by Paul Hammant
- Harness: A Complete Guide to Trunk-Based Development - Implementation guide with practical workflows
- Atlassian: Trunk-Based Development - Team adoption and workflow guidance
- Statsig: Trunk-Based Development in Git - Git-specific technical guidance
- ProductDock: Trunk-Based Development Guide 2025 - Modern perspective on TBD practices