The rules: 1. Branch from main. 2. Make small changes. 3. Merge back to main within 2 days. 4. Use feature flags to hide incomplete work. 5. Never have a branch older than 2 days without merging.
✦ Definition~90s read
What is Trunk-Based Development?
Trunk-based development is a version control practice where developers merge small changes to main (trunk) multiple times per day. Feature branches are short-lived (hours to at most 2 days), and long-lived branches are prohibited.
★
Imagine a highway where everyone merges into the same lane (main) every few minutes.
Plain-English First
Imagine a highway where everyone merges into the same lane (main) every few minutes. Traffic flows smoothly because each merge is small. Now imagine a highway where 40 cars drive in their own lanes for weeks, then all try to merge at once. That's feature branches. Trunk-based development is the 'merge early, merge often' approach that prevents merge hell.
Long-lived feature branches are the #1 cause of merge pain in Git. Two developers working on separate branches for 3 weeks will have conflicts when they merge — guaranteed. The codebases diverged too far. Trunk-based development solves this by merging to main multiple times daily. Incomplete features are hidden behind feature flags (if/else blocks that toggle features on/off). The result: no merge hell, continuous integration catches conflicts immediately, and deployment can happen at any time. This guide covers the trunk-based workflow, the incident that triggered a 3-day merge catastrophe, and how to migrate from feature branches to trunk-based development.
Trunk-Based Development Rules and Practices
The core rules are simple but require discipline:
Branch from main, merge to main: Every branch starts from main's latest commit. Every branch merges back to main.
Short branch lifetime: A branch should exist for hours, not days. 2 days is the maximum. If your feature takes longer, break it into smaller chunks or use feature flags.
Feature flags: Hide incomplete features behind toggles. if feature_flag_enabled('new-payment'): new_payment() else: old_payment(). The flag defaults to 'off' until the feature is complete and tested.
CI must pass before merge: Every merge to main must pass CI. This prevents broken main from blocking other developers.
No direct commits to main: All changes go through short-lived branches reviewed via PR. Main is always in a deployable state.
Benefits: no merge hell, continuous integration catches conflicts immediately, team velocity increases as merge overhead disappears.
01_trunk_workflow.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Start from latest main
git checkout main
git pull --rebase origin main
git checkout -b feat/email-validation
# Make small changes (1-2 commits)
echo 'validate() { return true }' > email.js
git add . && git commit -m 'feat: add email validation'
# Merge to main within hours (not days)
git checkout main
git pull --rebase origin main
git merge --squash feat/email-validation
git commit -m 'feat: add email validation'
git push
# For incomplete features: use feature flags
# config.js: export constFEATURES = { newPayment: false }
# app.js: if (FEATURES.newPayment) { showNewPayment() } else { showOldPayment() }
# Delete the branch
git branch -d feat/email-validation
Feature Flags Add Complexity — Manage Them Carefully
Feature flags introduce conditional code paths. Too many flags create 'flag debt': dead code paths that are never cleaned up. Maintain a list of active flags and their owners. Schedule regular cleanup: when a feature is 100% rolled out, remove the old code path and the flag. Tools like LaunchDarkly manage flags at scale.
Production Insight
Trunk-based development is a prerequisite for continuous deployment. If merging to main takes 3 days, deploying multiple times daily is impossible. Google, Netflix, and Facebook all use trunk-based development (or close variants). The key metric: time from 'branch created' to 'merged to main'. Aim for < 4 hours. If branches live longer than 2 days, something is wrong with your feature sizing or your team is avoiding merge pain by deferring it — which makes it worse.
Key Takeaway
Trunk-based dev: merge to main multiple times daily. Max branch lifetime: 2 days. Use feature flags for incomplete work. No merge hell, CI catches conflicts immediately, deployment is always possible.
● Production incidentPOST-MORTEMseverity: high
40 Feature Branches, 3 Days of Merge Hell, 1 Deleted Feature
Symptom
A team of 12 developers worked on 40 feature branches over 4 weeks. When they tried to merge to main before a release, the merge conflicts took 3 days to resolve. One feature was accidentally deleted during conflict resolution and wasn't noticed until after the deploy.
Assumption
The team assumed Git would handle all merges automatically. They didn't realize that 4 weeks of divergence across 40 branches would create conflicts in nearly every file.
Root cause
1. 12 developers started 40 feature branches from the same main commit. 2. Over 4 weeks, main received 50+ commits (hotfixes, other features merged via different process). 3. Each feature branch drifted further from main. 4. Merge conflicts accumulated: conflicting changes to shared files, deleted functions, renamed variables. 5. When branches were merged sequentially, each merge resolved old conflicts but created new ones for the next branch. 6. After 3 days of conflict resolution, one developer accidentally kept the wrong version of a file, deleting an entire feature's changes.
Fix
1. Identified the missing feature via git diff of the final merge against expected state. 2. Used git log --all --full-history -- <deleted-file> to find the last commit that had the feature. 3. Cherry-picked the missing feature commits back. 4. Initiated trunk-based development: max branch lifetime 2 days, feature flags for incomplete work, mandatory daily merges. 5. Set up CI that flags branches older than 2 days without merging to main.
Key lesson
Long-lived branches cause merge hell.
Trunk-based development: merge to main multiple times daily.
Use feature flags for incomplete work.
Max branch lifetime: 2 days.
CI should flag stale branches.
Production debug guideUse this when production is on fire3 entries
Symptom · 01
Team has long-lived feature branches with merge conflicts
→
Fix
Switch to trunk-based development: merge to main within 2 days
Symptom · 02
Feature is too big to complete in 2 days
→
Fix
Use feature flags to hide incomplete work, merge the partial code to main
Symptom · 03
Team is resistant to more frequent merging
→
Fix
Start with 1-week max branch lifetime, then reduce to 2 days
Key takeaways
1
Branch from main, merge to main within 2 days maximum
2
Use feature flags to hide incomplete work
3
CI must pass before every merge
4
Trunk-based development enables continuous deployment