ScaledByDesign/Insights
ServicesPricingAboutContact
Book a Call
Scaled By Design

Fractional CTO + execution partner for revenue-critical systems.

Company

  • About
  • Services
  • Contact

Resources

  • Insights
  • Pricing
  • FAQ

Legal

  • Privacy Policy
  • Terms of Service

© 2026 ScaledByDesign. All rights reserved.

contact@scaledbydesign.com

On This Page

The Branch That Lived Too LongWhy Long-Lived Branches FailWhat Trunk-Based Development Actually IsSafety Net 1: Feature FlagsSafety Net 2: Continuous Integration That Actually WorksSafety Net 4: Automated RollbacksSafety Net 5: ObservabilityThe ResultsWhen Not to Use Trunk-Based Development
  1. Insights
  2. Engineering
  3. Trunk-Based Development Actually Works — If You Do These 5 Things

Trunk-Based Development Actually Works — If You Do These 5 Things

February 20, 2026·ScaledByDesign·
gitci-cdtrunk-based-developmentengineering-practices

The Branch That Lived Too Long

We audited a 30-person engineering team that was shipping once every two weeks and couldn't figure out why. The product team was frustrated. The CEO was frustrated. The engineers were frustrated.

We looked at their git history. They had 47 open feature branches. The oldest was 3 months old. Every merge was a multi-day event involving manual testing, conflict resolution, and prayer.

They had a branching strategy. It was called "everyone works in isolation and we figure it out later." And "later" was always painful.

Why Long-Lived Branches Fail

The math is simple. Merge conflict probability increases exponentially with:

  1. Number of files changed — More changes = more potential conflicts
  2. Duration of the branch — Longer branch = more main branch changes to conflict with
  3. Number of parallel branches — More branches = more things to conflict with each other
Branch Duration vs Merge Pain:

  1 day branch:   ~5% chance of conflict, ~10 min to resolve
  1 week branch:  ~30% chance of conflict, ~2 hours to resolve
  2 week branch:  ~60% chance of conflict, ~1 day to resolve
  1 month branch: ~90% chance of conflict, ~3 days to resolve
  3 month branch: ~99% chance of conflict, "we should just rewrite it"

Long-lived branches also create a hidden problem: integration risk. You don't know if your code works with everyone else's code until you merge. By that point, you've invested weeks of work into something that might not integrate cleanly.

What Trunk-Based Development Actually Is

Trunk-based development (TBD) means everyone commits to the main branch (trunk) frequently — at least once per day, ideally multiple times. There are no long-lived feature branches.

Traditional branching:
  main ─────────────────────────────────────────────
    \                                        /
     feature/auth ──────────────────────────
    \                           /
     feature/search ───────────
    \                    /
     feature/checkout ──

Trunk-based development:
  main ═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══╦═══
          │   │   │   │   │   │   │   │   │   │
          A   B   A   C   B   A   B   C   A   B
          (commits from different developers, daily)

"But what about unfinished features?" Great question. That's where the five safety nets come in.

Safety Net 1: Feature Flags

Feature flags are the single most important enabler of trunk-based development. They let you merge incomplete code without exposing it to users:

// Feature flag wrapping an incomplete feature
export function SearchResults({ query }: { query: string }) {
  const flags = useFeatureFlags();
 
  if (flags.isEnabled("new-search-algorithm")) {
    return <NewSearchResults query={query} />;
  }
 
  return <LegacySearchResults query={query} />;
}
// Server-side feature flags for API changes
app.get("/api/search", async (req, res) => {
  const flags = await getFlags(req.user);
 
  if (flags.isEnabled("elasticsearch-backend")) {
    const results = await elasticSearch.query(req.query.q);
    return res.json(results);
  }
 
  // Legacy path — still works, always available
  const results = await postgresSearch.query(req.query.q);
  return res.json(results);
});

Feature flags turn deployment into a non-event. You deploy code to production multiple times per day. You enable features when they're ready — a separate decision from a separate system.

Deploy ≠ Release

Deploy: Code goes to production (happens constantly, automated)
Release: Feature becomes visible to users (happens when ready, controlled)

Safety Net 2: Continuous Integration That Actually Works

CI is not "run the tests when someone opens a PR." In trunk-based development, CI runs on every commit to main. It must be fast, reliable, and comprehensive:

# .github/workflows/ci.yml
name: Trunk CI
on:
  push:
    branches: [main]
 
jobs:
  fast-checks:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v4
      - run: npm ci --prefer-offline
      - run: npm run typecheck    # Must pass
      - run: npm run lint         # Must pass
      - run: npm run test:unit    # Must pass, must be fast (<2 min)
 
  integration-tests:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    needs: fast-checks
    steps:
      - uses: actions/checkout@v4
      - run: npm ci --prefer-offline
      - run: npm run test:integration
 
  deploy-staging:
    needs: [fast-checks, integration-tests]
    runs-on: ubuntu-latest
    steps:
      - run: npm run deploy:staging

The critical requirement: CI must finish in under 10 minutes. If it takes 30 minutes, developers will batch up changes to avoid waiting. Batching up changes defeats the entire purpose.

CI Speed Targets:
  Lint + typecheck:     < 1 minute
  Unit tests:           < 3 minutes
  Integration tests:    < 10 minutes
  Full pipeline:        < 15 minutes

## Safety Net 4: Automated Rollbacks

When you deploy to main multiple times per day, you need automatic rollback capabilities:

```typescript
// Deployment health check with automatic rollback
const deploymentConfig = {
  strategy: "canary",
  canaryPercent: 10,        // Start with 10% of traffic
  healthChecks: {
    errorRate: { threshold: 0.5, window: "5m" },  // <0.5% error rate
    latencyP99: { threshold: 500, window: "5m" },  // <500ms p99
    successRate: { threshold: 99.5, window: "5m" }, // >99.5% success
  },
  promotionSteps: [10, 25, 50, 100],  // Gradual rollout
  rollbackOnFailure: true,             // Automatic
};

If any health check fails, the deployment automatically rolls back to the previous version. No human intervention required. No 2am pages. The system protects itself.

Safety Net 5: Observability

You can't deploy confidently if you can't see what's happening. Every commit to main should be observable:

// Structured logging tied to deployments
const logger = createLogger({
  defaultMeta: {
    service: "api",
    version: process.env.COMMIT_SHA,
    deployedAt: process.env.DEPLOY_TIMESTAMP,
  },
});
 
// Deployment markers in your monitoring
await datadog.createEvent({
  title: `Deploy ${commitSha.substring(0, 7)}`,
  text: `Deployed by ${deployer}. Changes: ${commitMessage}`,
  tags: ["deployment", `env:${environment}`],
});

When something breaks, you need to answer "which commit caused this?" in seconds, not hours.

The Results

That 30-person team with 47 feature branches? Here's what happened after adopting trunk-based development:

MetricBefore (Feature Branches)After (Trunk-Based)
Deploy frequencyEvery 2 weeks4-8 times/day
Lead time (commit to production)2-3 weeks30 minutes
Merge conflicts per week12-151-2
Rollbacks per month0 (couldn't rollback)3-4 (easy, fast)
Time spent on merge conflicts15 hours/week1 hour/week
Developer satisfaction3.2/107.8/10

The team went from shipping 2 features per sprint to shipping 8-10. Not because they worked harder — because they stopped wasting time on branching overhead.

When Not to Use Trunk-Based Development

TBD isn't right for every situation:

  • Open source projects: External contributors need PRs and review before merge
  • Heavily regulated industries: Some compliance frameworks require branch-based review workflows
  • Very junior teams: If the team can't write atomic commits or use feature flags, start with short-lived branches (1-2 days max) and work toward TBD

But for most product engineering teams? Long-lived feature branches are a crutch. Trunk-based development is what shipping looks like when you do it right.

Stop branching. Start shipping.

Previous
Your Post-Purchase Experience Is Leaving $2M on the Table
Insights
Trunk-Based Development Actually Works — If You Do These 5 ThingsHow to Write RFCs That Actually Get ReadThe Engineering Ladder Nobody Follows (And How to Fix It)Why Your Best Engineers Keep LeavingCode Review Is a Bottleneck — Here's How to Fix ItThe Incident Retro That Actually Prevents the Next IncidentRemote Engineering Teams That Ship: The PlaybookHow to Run Execution Sprints That Actually ShipThe On-Call Rotation That Doesn't Burn Out Your TeamTechnical Interviews Are Broken — Here's What We Do Instead

Ready to Ship?

Let's talk about your engineering challenges and how we can help.

Book a Call