Trunk-based development isn’t about merging code more often; it’s about merging code to the main branch more often.

Let’s see it in action. Imagine a team working on a web application. Their main branch is always deployable.

Here’s a simplified Git log showing a common workflow:

* 1234567 (HEAD -> main, origin/main) Merge pull request #105 from feature/add-user-profile
|\
| * abcdef0 (feature/add-user-profile) Implement user profile display
* 7890123 (feature/search-pagination) Add pagination to search results
| * fedcba9 (feature/search-pagination) Refactor search service
|/
* 3456789 (tag: v1.2.0) Release v1.2.0
* aabbcc1 Add basic authentication

In this scenario, feature/add-user-profile was merged directly into main. The team likely used short-lived feature branches, perhaps even directly committing to main with robust testing and automated checks in place. Notice feature/search-pagination also exists, but it hasn’t been merged yet. The key is that main remains clean and deployable.

The core problem trunk-based development solves is the overhead and risk associated with long-lived feature branches and complex, infrequent merges. Think about the pain of a "merge hell" where you’re trying to integrate weeks or months of changes into a rapidly evolving main branch. It’s a recipe for bugs, lost productivity, and delayed releases. Trunk-based development eliminates this by keeping the integration point (the trunk, usually main) healthy and frequently updated.

Internally, it relies heavily on a few foundational practices:

  1. Continuous Integration (CI): Every commit to main (or a short-lived branch that’s about to be merged) triggers an automated build, test suite, and potentially static analysis. This catches integration issues almost immediately.
  2. Automated Testing: A comprehensive suite of unit, integration, and end-to-end tests is non-negotiable. Without this safety net, merging frequently to main would be reckless.
  3. Feature Flags (or Feature Toggles): This is the secret sauce for enabling incomplete features on main without impacting users. Code for a new feature can be merged into main but kept "off" for all users via a configuration flag. This allows the code to be tested and integrated continuously while development continues.

The levers you control are primarily around your CI/CD pipeline and your branching strategy. Instead of creating feature/my-awesome-thing and working on it for two weeks, you might:

  • Direct Commits to main (with strong CI/CD): For very small, well-understood changes, commit directly to main. Your CI pipeline must be fast and robust.
  • Short-Lived Branches: Create a branch, make a small, focused change, run local tests, and immediately open a pull request (PR) to merge into main. The PR is reviewed and merged within hours, not days.
  • Feature Flags: Wrap all new development in feature flags. Merge the flagged code to main as soon as it’s ready for integration, even if it’s not fully complete. This keeps your main branch always deployable and reduces merge conflicts dramatically.

Here’s a look at what a feature flag might look like in code:

# config/features.py
FEATURE_TOGGLES = {
    "new_user_profile_page": True, # Set to False to disable
    "experimental_search_algorithm": False,
}

# app/views.py
from config.features import FEATURE_TOGGLES

def render_user_profile(user_id):
    if FEATURE_TOGGLES.get("new_user_profile_page", False):
        return render_template("new_profile.html", user=get_user(user_id))
    else:
        return render_template("old_profile.html", user=get_user(user_id))

The ability to merge code that isn’t quite ready for production, but is ready for integration and testing, is what makes trunk-based development practical. You can have a partially implemented feature on main, tested by your CI, but hidden from users via a flag. When the feature is fully ready, you simply flip the flag to True and deploy. This decouples the "merge to main" event from the "release to users" event, which is a critical distinction.

The next concept you’ll encounter is how to manage the lifecycle of these feature flags, especially when they become stale or when you need to roll back a feature quickly.

Want structured learning?

Take the full Git course →