Branching Strategies

Trunk-based, GitHub flow and GitFlow compared on the only axis that matters — how long a branch lives — plus feature flags, release branches and hotfix paths.

intermediate 20 min lesson hands-on task included

Every branching strategy is an answer to one question: how long may a branch live before merging? Everything else — the diagrams, the branch names, the ceremony — follows from that number.


Topic 1: Three Strategies, One Axis

THREE STRATEGIES — THE DIFFERENCE IS HOW LONG A BRANCH LIVES TRUNK-BASED — branches live hours main Small merges, many per day, feature flags hide unfinished work. Fewest conflicts, needs real CI and real tests. GITHUB FLOW — branches live days main One long-lived branch, short feature branches, PR review, deploy from main. The default for most teams. GITFLOW — branches live weeks main/tags develop feature release/hotfix Built for versioned, shipped-on-a-schedule software. On a service deployed daily it adds merge pain and buys nothing.
Read them as three points on a single scale of branch lifetime. The ceremony in GitFlow exists because branches live for weeks; remove that constraint and most of the ceremony has no job.

Trunk-based — branches live hours. Everyone integrates into main at least daily. Unfinished work hides behind feature flags. Fewest conflicts of any strategy, and the highest bar for supporting practice: you need fast, trustworthy CI and a real ability to deploy from main at any moment.

GitHub flow — branches live days. One long-lived branch (main), short feature branches, PR review, deploy from main. The pragmatic default for a service deployed continuously, and what most teams should use.

GitFlow — branches live weeks. main, develop, feature/*, release/*, hotfix/*. Designed for versioned software shipped on a schedule to customers who install it. On a web service deployed daily it adds a permanent merge tax and buys nothing.

Trunk-basedGitHub flowGitFlow
Long-lived branches112 (main + develop)
Branch lifetimeHoursDaysWeeks
Merge conflictsRareOccasionalFrequent and large
Requires feature flagsYesSometimesNo
Requires strong CIYesYesHelps
Supports many live versionsPoorlyPoorlyYes
Release cadenceContinuousContinuousScheduled

The honest recommendation: GitHub flow unless you have a specific reason. Trunk-based if you have the CI and flag discipline to support it and want the fastest integration. GitFlow only if you genuinely ship versioned releases and support more than one at a time — a mobile app with a review process, an on-premises product, firmware.


Topic 2: Why Long-Lived Branches Cost More Than They Look

The cost is not linear in time, which is why “just one more week” is such a reliable trap:

  • Conflicts grow superlinearly. Two weeks of divergence is far more than twice the pain of one.
  • Feedback is delayed. A design flaw found at merge time is a rewrite; found on day one it is a conversation.
  • CI tests a fiction. The branch is green against a main that no longer exists.
  • Review degrades. A 3,000-line diff receives approval, not review.
  • The merge becomes an event — scheduled, feared, and requiring the author to be present.

The fix is almost never a better branching strategy. It is smaller changes: merge the refactor separately, merge behind a flag, merge the interface before the implementation.


Topic 3: Feature Flags — the Enabler for Short Branches

A feature flag decouples merging from releasing, which is what makes daily integration of unfinished work possible:

if flags.enabled("new-checkout", user):
    return new_checkout(user)
return old_checkout(user)

What that buys: incomplete code merges to main safely, the feature turns on for 1% then 50% then everyone, and turning it off is a config change rather than a rollback.

What it costs, and this is the part teams underestimate: every flag is a branch in the code, and n flags mean 2ⁿ possible states, of which you test perhaps three. Flags that outlive their rollout become permanent complexity.

The discipline that keeps it manageable: an owner and an expiry date per flag, a periodic sweep that deletes flags fully rolled out, and a hard rule that a flag lives weeks, not quarters. A repository with forty flags has traded merge pain for a worse kind of complexity.


Topic 4: Release Branches and Hotfixes

Even trunk-based teams need a release branch when they must support a version they cannot immediately update.

git switch -c release/2026.08 main
git tag -a v2026.08.0 -m "Release 2026.08.0"
git push origin release/2026.08 --tags

The rule for fixes: always fix on main first, then cherry-pick to the release branch. Not the other way round.

# fix lands on main as 9f8e7d
git switch release/2026.08
git cherry-pick -x 9f8e7d          # -x records the source commit
git tag -a v2026.08.1 -m "Fix token expiry"
git push origin release/2026.08 --tags

Fixing on the release branch first and merging back is how a bug gets re-introduced in the next release: somebody forgets the merge-back, and the fix exists only on a branch that is about to be abandoned. Fixing forward and cherry-picking backward means the fix is on main by construction.

Emergency hotfix, same principle, compressed:

1. Branch from the TAG that is in production, not from main
2. Minimal fix, tested
3. Tag, deploy
4. Cherry-pick or merge the fix into main immediately — before the incident review

Step 4 is the one skipped under pressure, and skipping it means the next deploy silently reverts the hotfix. Put it in the incident checklist.


Topic 5: Monorepo vs Many Repositories

The other structural decision, and it interacts with everything above.

MonorepoMulti-repo
Atomic cross-project changeOne commitCoordinated PRs, ordering matters
Dependency versionsOne version, always currentPinned, and drift accumulates
CINeeds change-detection to stay fastNaturally scoped
Access controlAll-or-nothing (Git’s limitation)Per repository
Clone sizeGrows with everythingSmall
ToolingNeeds investment: sparse checkout, build graphWorks out of the box

Neither is right in general. What is reliably wrong is a monorepo without the tooling investment — one that clones for eleven minutes and runs every test on every change — or a multi-repo estate where a single logical change requires five coordinated merges in a specific order.

The Git features that make a large monorepo bearable — sparse checkout, partial clone, worktrees, scheduled maintenance — are covered in the repository-scale lesson.


Topic 6: Writing the Convention Down

A strategy that lives in people’s heads is not a strategy. What the document should contain, and nothing more:

Branch names           feature/*, fix/*, chore/*, release/*
Base branch            always main
Merge strategy         squash (one of the three, chosen)
Branch lifetime        merge within 2 days, or explain in the PR
Required checks        build, unit, lint — enforced by branch protection
Review                 1 approval, CODEOWNERS for protected paths
Release                tag from main, semver, annotated tags only
Hotfix                 branch from the tag, fix, tag, cherry-pick to main
Flags                  every flag has an owner and a removal date

Then enforce it in the forge, not in review comments. Branch protection rules, required status checks, an allowed-merge-strategy setting, automatic branch deletion. A convention enforced by a human is a convention that erodes on the first busy week.

Try it yourself: compute the median lifetime of your last fifty merged branches. Teams almost always guess a number well below the real one, and that gap — not the diagram on the wiki — is what actually determines how much merge pain the team experiences.

Common mistake: adopting GitFlow because it appears in every search result, on a service that deploys twelve times a day. The develop branch becomes a second main that must be kept in sync, every change is merged twice, release branches linger, and the team concludes that Git is complicated. The strategy is not wrong; it is answering a question — how do we support three shipped versions at once — that the team does not have.