Skip to content

Trunk-Based Development

Published: at 09:02 PM

In large development teams, managing feature branches and core reviews can become a bottleneck, especially when pull requests grow large and require approval from multiple reviewers or development teams. This often leads to delayed integrations, increased changes of merge conflicts, and longer feedback loop. Trunk-based development (TBD) enabling faster feedback, better collaboration, and a more stable codebased. In this article, we’ll explore what trunk-based development is, why it matters, and how it can be effectively adopted in iOS development workflows.

Table of contents

Open Table of contents

Introduction

In large development teams developing new features often comes with significant challenges. When a task is carried out in a separate branch over the course of several days or even weeks, changes accumulate, and by the time code review happens, the following problems typically arise:

As a result, new features reach users more slowly, and developers spend more time dealing with complex integrations instead of delivering value.

One solution to this problem is Trunk-based development (TBD) - an approach where small increments are integrated directly into the main branch. To safely introduce unfinished or partially completed features, TBD heavy relies on feature toggles - switches that allows functionallity to be enabled or disabled on the fly without requiring the code to be “production ready” at the moment of commit.

Problems TBD Aims to Solve

The traditional workflow of developing features in isolated branches often leads to hidden inefficiencies. Long-lived branches and delayed merges create a fragile integration process where:

These problmes don’t just affect individual productivity - they slow down the entire team’s ability to innovate, respond to user feedback, and ship improvement quickly.

The TBD’s goal is to remove these hidden costs by changing how and when code is integrated, not just what is being developed.

A High-Level Overview of Trunk-Based Development

Trunk-based development is a development approach where all developers work in a shared main branch (the “trunk”) and integrate small, incremental changes continously.

The philosophy is the longer your branch lives, the greater the rish.

Key practicies of TBD:

TBD shifts the focus from isolated delivery to continuous collaboration, reducing integration risks, speeding up feedback, and enabling a halthier, faster development cycle.

Core Principles of Trunk-Based Development

Trunk-based development is a version control management practice where developers merge small, frequent updates to a core “trunk” or main branch. Atlassian

Trunk-based development is built around a few fundamental principles that shape how teams collaborate and deliver software:

The goal of these principles is to create a flow where integration is continuous and natural, rather than a painful, risky event at the end of a features’s lifecycle.

How TBD Differs from GitFlow

At first glance, GitFlow and TBD might seem similar - both involve branching and merging - but their philosophies are fundamentally different.

AspectGitFlowTrunk-Based Development
Branching ModelHeavy use of long-lived feature, release, and hotfix branches.Minimal branching, frequent commits to trunk (main).
Integration TimingFeatures are integrated after they are fully completed.Features are integrated in very small increments throughout development.
Release ManagementSeparate release branches are maintained.Main branch is always production-ready; releases are cut directly from main.
Risk LevelHigh risk during late-stage integrations.Low risk through continuous integration.
Developer WorkflowDevelopers work independently for long periods.Developers collaborate and integrate continuously.

In GitFlow, merging a large feature can feel like a “big event” that requires careful planning and testing. In TBD, merging is a daily routine — it’s quick, safe, and largely uneventful.

Working with Short-Lived Branches

The core principle is that changes should be integrated into the main branch frequently and in small increments. There are two main approaches for achieving this: short-lived branches and direct commits to main. Both approaches aim to reduce the risks associated with large, long-lived feature branches and increase the speed of development.

Short-Lived Branches

Short-lived branches are typically used for a few hours or up to a day, never staying open for more than a couple of days. These branches are focused on specific tasks, and the goal is to integrate them back into the main branch as quickly as possible.

When to use short-lived branches:

How to manage short-lived branches:

Direct Commits to Main

In Trunk-based development, commiting directly to the main branch is considered the best practice for smaller, self-contained changes. This approach is quicker and helps maintain the stability of the trunk, as it avoids the overhead of creating and merging branches.

Best practices for commiting directly to main:

Balancing Short-Lived Branches and Direct Commits

Choosing between short-lived branches and direct commits to main depends on the size and scope of the change. For larger changes or when collaborating with other developers, short-lived branches might make sense. However, for smaller, independent changes that can be tested quickly, direct commits to main are often faster and more efficient.

In practice, developers ofter use a mix of both approaches:

Ultimately, the key to success in TBD is integration over isolation. Whether you’re commiting directly to the main branch or using short-lived branches, the goal is always to keep codebase moving forward with minimal delay and maximum stability.

How to Safely Deploy Features

One of the core principles of Trunk-based development is maintaining stability of the main branch at all times.

Every commit to main must be safe and should not break the production version of the app. This requires discipline, careful testing, and proper feature isolation technique. Without this approach, even small changes can cause: unexpected bugs or crashes, user-facing problems (user may encountered broken interfaces, missing features, or crahses, which can damage their trust in the app), difficult rollback process (if the code hasn’t been properly tested and isolated, reverting changes can become a complicated and risky task, especially when a new feature interacts with multiple components of the system).

To avoid these issues, new features should be deployed in a controlled and safe manner using techniques like feature toggles and other strategies that allow for enabling and disabling functionality without risking the stability of the code.

The Practice of Dark Launching

Dark launching is a strategy where new features or functionality are deployed to production but remain hidden from end users until they are fully tested and ready. This allows team to deploy the feature in a real-world environment without affecting the user experience.

With dark launching, teams can:

How it works in practice:

Example: Imagine you’re adding a new filtering screen into the app. You write the code for the screen but don’t want users to see it until it’s fully finished. You deploy the code with a feature flag (for example, using Firebase Remote Config or server-side flags) that keeps the feature hidden. You can then test the feature in production environment by enabling it only for a small group of beta testers or developers. Once the feature is polished , you can easily roll it out to all users by flipping the feature flag.

Benifits of dark launching:

Using feature toggles and dark launching strategies enables safe and controlled feature deployment without breaking production. These practicies are essential in TBD, ensuring a smoother development process and quicker integration of new features with minimal risks.

In the next section, we will discuss how to practically implement feature flags for deploying new features in iOS projects and how this works within the iOS ecosystem.

Feature Toggles as a Foundation of TBD

In Trunk-based development, the ability to safely commit incomplete or experimental code to main without affecting end users is crucial. This is achieved primarly through the use of feature toggles. Feature toggles allow teams to merge work early and often, maintain a stable production-ready branch, and control feature visibility dynamically without needing hotfixes or emergency rollbacks.

There are two main types of feature toggles commonly used in development: Local Feature Toggles and Remote Feature Toggles.

Local Feature Toggles

Local feature toggles are controlled entirely within the app itself, without depending on external servers.

Using UserDefaultsor other local storage. A simple and effective way to implement local toggles in iOS is by leveraging UserDefaults, in-memory configurations, or app settings.

For example:

struct FeatureFlags {
    static var isNewFilterScreenEnabled: Bool {
        return UserDefaults.standard.bool(forKey: "isNewFilterScreenEnabled")
    }
}

In development builds, engineers can easily toggle features on or off via UserDefaults, hidden developer settings, or even custom debug menus.

There are possible alternatives to UserDefaults:

Example usage:

if FeatureFlags.isNewFilterScreenEnabled {
  showNewFilterScreen()
} else {
  showOldFilterScreen()
}
ProsCons
Extremely fast setup.No ability to change behavior remotely after release.
No external dependencies or network calls.Requires an app update to toggle features in production.
Useful for development, debugging, or QA testing.Less effective for real user experimentation (no A/B testing).

Local toggles are ideal for development and early internal testing but are limited when it comes to controlling features post-release.

Remote Feature Toggles

Remote feature toggles are controlled by external servers or configuration management platforms, such as Firebase Remote Config, LaunchDarkly, Optimizely, or custom backend solutions.

With remote feature flags, the app fetches configuration values from the server to determine feature availability at runtime.

For example:

import FirebaseRemoteConfig

let remoteConfig = RemoteConfig.remoteConfig()
remoteConfig.fetch { status, error in
    if status == .success {
        remoteConfig.activate()
        let isNewFeatureEnabled = remoteConfig["isNewFilterScreenEnabled"].boolValue
        if isNewFeatureEnabled {
            showNewFilterScreen()
        }
    }
}

This enables dynamic control over features without requiring a new app release.

Remote toggles are especially powerful because they support:

This approach helps catch critical bugs early with limited exposure and gather analytics to inform product decisions.

Example: Enable a new onboarding flow for 5% of new users in a specific region before scaling it worldwide.

Remote feature toggles embody the philosophy of “release early, control remotely”. You can deploy your code to production as soon as it’s technically ready, even if the business decision to fully launch the feature hasn’t been made yet.

This gives the team flexibility to decouple code deployment from feature branches, speed to merge and deliver code continuously, safety to react quickly if something goes wrong(just disable the feature remotely).

Feature toggles are a critical enabler of Trunk-based development. Local feature toggles are great for development and internal testing phases. Remote toggles provide production-grade control, allowing for safe launches, fast experimentation, and quick responses to issues.

CI/CD and Automation

In Trunk-based development, rapid integration is a key. To make it safe and efficient, teams must rely heavily on Continuous Integration and Continuous Deployment pipelines that automatically verify every change before it is merged into main.

Automation ensures that every commit is validated quicly and reliably, reducing the risk of introduction regressions or breaking the app.

In TBD, developers commit quickly to main frequently - sometimes multiple times per day. Without strong CI processes in place, the risk of introducing bugs grows rapidly. That is why immediate feedback on every commit is crucial.

A good CI system will:

Essentially, CI becomes the team’s safety net. It allows fast-moving development without sacrificing code quality.

To maintain stability and quality when practicing TBD, your CI must enforce several key cheks:

  1. Unit Tests
  1. UI Tests (Optional but Recommended)
  1. Static Analysis and Linters
  1. Build Validation

Example setup for XCode

Fastlane is a popular open-source automation tool used to simplify many tasks in iOS development, such as:

default_platform(:ios)

platform :ios do
  desc "Run tests"
  lane :test do
    scan(
      workspace: "YouExampleApprApp.xcworkspace",
      scheme: "ExampleApp",
      clean: true,
      code_coverage: true
    )
  end

  desc "Build the app"
  lane :build do
    gym(
      workspace: "ExampleApp.xcworkspace",
      scheme: "ExampleApp",
      clean: true
    )
  end
end

A minimal .github/workflows/ci.yml file for running tests might look like:

name: iOS CI

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  build-and-test:
    runs-on: macos-latest

    steps:
      - uses: actions/checkout@v3

      - name: Install dependencies
        run: bundle install

      - name: Run tests
        run: bundle exec fastlane test

This configuration will automatically:

For developers practicing Trunk-Based Development, choosing the right CI/CD platform is important to ensure fast feedback, easy scaling, and reliability.

There are many CI/CD solutions available for iOS development, such as GitHub Actions, Xcode Cloud, Bitrise, or self-hosted setups like Jenkins. Each offers different levels of flexibility, automation, and integration:

ToolDescription
GitHub ActionsProvides a flexible, GitHub-native way to define workflows using simple YAML files. It allows you to automate builds, tests, and deployments directly from your repository with minimal setup.
Xcode CloudA CI/CD service built by Apple, tightly integrated into Xcode and App Store Connect. It simplifies building, testing, and distributing iOS, macOS, watchOS, and tvOS apps without needing third-party tools.
BitriseA mobile-focused CI/CD platform offering prebuilt steps for typical iOS workflows such as installing dependencies, running tests, building apps, and deploying to TestFlight. It’s a managed solution that minimizes setup effort.
Self-hosted (e.g., Jenkins)Gives teams full control over the CI/CD infrastructure. Beneficial for large organizations with custom requirements, but requires maintaining servers and build environments.

A typical TBD-friendly CI pipeline for iOS projects usually includes:

Well-configured CI/CD pipelines are the backbone of an efficient TBD workflow, allowing teams to maintain a high development velocity while ensuring that every change is safe to ship.

Managing the Lifecycle of Feature Toggles

Feature toggles are a core part of Trunk-based development, but managing them properly is critical to avoid technical debt and maintain a clean codebase over time.

When introducing a feature toggle, it’s important to clearly define its purpose: whether it is meant for a dark launch, an A/B test, a staged rollout, or as a kill switch. Teams should also agree on whether the toggle is intended to be temporary or permanent, and assign clear ownership to ensure someone is responsible for monitoring and removing it later.

In the implementation phase, toggles should be kept simple, localized, and easy to find. If dynamic control over the feature is needed after release, server-side toggles using services like Firebase Remote Config or LaunchDarkly are preferable. For features still in development or for local testing, simpler approaches like UserDefaults-based toggles are usually sufficient. Regardless of the method, maintaining consistency in naming and documentation helps future developers quickly understand the toggle’s role.

After a toggle is deployed, it’s important to actively monitor how the feature behaves. Teams should track usage metrics, collect logs, and watch for errors or performance regressions tied to the feature. Remote toggles allow features to be turned off quickly if any critical issues arise, reducing the risk of widespread impact.

Once the feature behind a toggle is fully rolled out, tested, and stable, it’s essential to remove the toggle and the associated conditional code as soon as possible. Keeping old toggles around bloats the codebase, complicates testing, and increases the risk of introducing bugs during future development. Regularly scheduled reviews of existing toggles can help keep the code clean and maintain the fast iteration speed that Trunk-based development relies on.

In short, disciplined feature toggle management is key to maintaining a healthy and agile development process. Feature flags should be treated as part of the product’s lifecycle: planned carefully, monitored actively, and cleaned up aggressively once their purpose has been fulfilled.

How to Introduce TBD in an Existing Team

Switching to Trunk-Based Development (TBD) in an established team can be a big shift, but it’s absolutely doable with the right approach. It’s not just about changing how we write code — it’s about adapting the way we work together as a team. Here’s how you can make the transition smoother:

Gradual Transition. You don’t have to jump in all at once. If your team is used to working with long-lived feature branches, moving to TBD can feel like a big change. It’s better to ease into it. Start by making smaller, more frequent commits to main and integrating incomplete features using feature toggles. Once the team gets used to this, you can gradually eliminate long-lived branches and rely more on TBD. This way, you don’t overwhelm anyone, and the transition feels more natural.

Working with the Review Culture and Trust. One of the biggest changes with TBD is how we approach code reviews. In a typical workflow, you might be used to waiting for one big review at the end of a feature. But with TBD, we commit to main much more frequently, and reviews happen faster but more often. This means the review process will change: instead of one big review, there will be many smaller ones. It requires trust — trust that your code can be reviewed quickly, and trust in your teammates to provide feedback in real-time. As a team, you’ll need to get comfortable with this faster pace and shift your mindset from “perfecting before committing” to “iterating fast and improving with every commit.”

Minimizing Feature Branch Lifespan. With TBD, feature branches should be as short-lived as possible. Ideally, no feature branch should live longer than 1-2 days. This is because the longer a branch stays open, the more likely it is to cause conflicts and drift away from main. Keeping branches short and integrating changes frequently makes everything more manageable. You’ll find that the more often you commit, the less stress you’ll have around merging, and the more stable main will be.

Code Owners and Automating Reviews. To make reviews even faster and more efficient, consider using code owners and automation tools. Code owners are developers who are responsible for specific parts of the codebase, so they can quickly review changes in their area of expertise. Tools like GitHub Actions, Bitrise, or Xcode Cloud can automate many aspects of the process, such as running tests, linters, or static analysis on every commit. This means you catch basic issues right away, and your review process can focus on higher-level feedback rather than fixing small bugs.

By making these small but impactful changes, your team can smoothly transition to TBD without the stress. The key is to take it step by step, embrace the fast feedback cycle, and trust that automation and shorter branches will keep things running smoothly.

Potential Pitfalls

Despite all the benefits of Trunk-Based Development, there are some potential pitfalls you should be aware of. These challenges usually stem from inadequate planning or tooling, and they can slow down your progress or create technical debt if not addressed properly. Let’s look at a couple of key issues that might arise when adopting TBD.

TBD encourages frequent commits to main, but this only works if the codebase is continuously tested and stable. Without a solid testing foundation, committing often can result in broken builds or bugs sneaking into production. High-quality, comprehensive tests (unit, integration, UI) are absolutely crucial for this approach to succeed. If your test coverage is insufficient, you might face a situation where fast commits lead to unstable code and a messy main branch. It’s essential to invest in automated testing to catch issues early and maintain the quality of the code as you commit more frequently. Without reliable tests, TBD could do more harm than good.

Another common pitfall when using TBD is neglecting to properly manage feature toggles. While feature flags are essential for controlling unfinished or experimental features, they can quickly turn into technical debt if not handled well. For example, if your team relies on custom-built solutions for feature flags without using a more robust tool, it can lead to confusion and messy code. Over time, forgotten or outdated flags can clutter the codebase, making it harder to maintain and potentially causing bugs. It’s important to choose the right tools for managing feature flags, whether that’s Firebase Remote Config, LaunchDarkly, or a self-hosted solution. A poor or nonexistent feature flag strategy can easily lead to accumulating technical debt, especially if flags are not removed after they’ve outlived their purpose.

Final Thoughts

Trunk-Based Development is a powerful methodology, but it’s important to remember that it’s not a one-size-fits-all solution. It’s a tool that can greatly improve your development speed, integration process, and code quality when implemented correctly, but it’s not the cure for every challenge a team might face. It requires a shift in mindset, a commitment to continuous testing, and good tool support, such as feature flag management. When these elements are in place, TBD can help your team move faster, minimize merge conflicts, and create a more stable codebase. However, if you’re missing some of these pieces, the transition might be harder, and the benefits may be harder to realize.

Key Takeaways and Recommendations:

By following these principles, TBD can be a huge advantage for teams looking to streamline their development processes and deliver features faster without sacrificing stability.

While TBD works well for many teams, it’s not always the best fit for every situation. There are a few scenarios where TBD might not be ideal:

Ultimately, TBD can work wonders for many teams, but it’s essential to evaluate your team’s needs and infrastructure before diving in. When implemented thoughtfully, it can lead to faster delivery, better collaboration, and a more robust codebase.

Thanks for reading

If you enjoyed this post, be sure to follow me on Twitter to keep up with the new content.


avatar

Nikita Vasilev

A software engineer with over 8 years of experience in the industry. Writes this blog and builds open source frameworks.


Next Post
Polynomial Hashes