nanda mochammad
Engineering Notes

Git branching strategies that scale: from a solo repo to a team of fifty

7 min read
Tagged Git

Most “Git is hard” stories are really “our branching strategy is hard” stories. The commands are the same everywhere: branch, merge, rebase. What actually differs between a calm repo and a chaotic one is how long branches live, who is allowed to push where, and when code becomes a release. Match that to your team’s size and release rhythm and Git goes quiet. Get it wrong and every Friday turns into a merge marathon.

This is a field guide to the four strategies that cover almost every project, from a repo only you touch to a codebase fifty engineers share, plus a way to tell which one you are actually in.

The mental model

A branch is a pointer, not a copy

Before any strategy, the one idea everything rests on: a Git branch is a lightweight, movable pointer to a commit. Creating one copies no files, and merging is usually cheap. The expensive thing is never the branch itself. It is divergence. The longer your branch and main evolve apart, the more the world has changed underneath you, and the harder the eventual merge becomes.

Diagram: a feature branch diverges from main, gains two commits, then merges back into main. main feature/login branch off commit · commit merge back
A feature branch is a set of commits pointing back to where it left main. Merge while the gap is small and it stays painless; let it drift for three weeks and you are merging against a moving target.

Every strategy below is, at heart, a different answer to one question: how do we keep divergence small?

Scale 01: Solo

Just you? Commit to main and tag releases

When you are the only contributor, ceremony is pure cost. There is no one to review against, no one whose work you’ll collide with. Commit straight to main, keep each commit a coherent step, and mark releases with tags so you can always answer “what shipped on the 1st?”

git add -A
git commit -m "Add offline cache for the transaction list"

# mark a release you can return to
git tag -a v1.4.0 -m "Offline transactions"
git push --follow-tags

Reach for a throwaway branch only when you want to try something risky without touching a working main, a spike you might delete. Branch, explore, then either merge or git branch -D it. No process, just a safety net.

Best for: Personal apps, prototypes, side projects, learning repos. Watch out: The day a second person joins is the day this stops being enough. Don’t let “it’s always been fine” outlive the solo phase.

Scale 02: Small team

2-8 people shipping continuously? GitHub Flow

This is the default I reach for on almost every team, and the one I’d recommend you start with if you’re unsure. There is exactly one long-lived branch, main, always deployable, and everything else is a short feature branch that exists only long enough to open a pull request.

# always branch from an up-to-date main
git switch main
git pull --ff-only

git switch -c feat/payment-retry
# …commit your work…
git push -u origin feat/payment-retry
# open a PR, get a review, merge, delete the branch

The whole loop (branch, PR, review, merge, deploy) usually closes inside a day or two. main stays protected (no direct pushes), CI runs on every PR, and because branches are small the reviews are small too.

Best for: Most product teams, startups, apps with continuous or near-continuous delivery.

Scale 03: Growing / continuous delivery

Many contributors, trunk must stay green? Trunk-based

As a team grows past roughly ten active committers, even short-lived feature branches start to queue up and collide. Trunk-based development pushes GitHub Flow to its logical end: branches live hours, not days, and unfinished work hides behind feature flags rather than behind a long branch.

git switch -c quick/rename-balance-label
# one or two commits, merged the same day — often behind a flag
// the half-built feature ships dormant, toggled on later
if FeatureFlags.newTransferFlow {
    NewTransferView()
} else {
    LegacyTransferView()
}

The payoff is that everyone integrates against a trunk that stays green and never drifts far. The cost is discipline: you need solid CI, fast tests, and a flagging habit. This is how large, fast-moving teams avoid merge gridlock.

Best for: Larger teams, mature CI/CD, organisations practising continuous deployment. Watch out: Without feature flags and fast automated tests, “commit to trunk constantly” becomes “break trunk constantly.”

Scale 04: Large / scheduled releases

Versioned, scheduled releases? Git Flow (with eyes open)

Some projects do need to maintain several versions at once: desktop software, SDKs, anything with a formal QA gate and a release calendar. Git Flow adds long-lived lanes for exactly this: an integration branch (develop), stabilisation branches (release/*), and emergency branches (hotfix/*) that patch production directly.

# integration happens on develop
git switch -c feature/loyalty-points develop

# stabilise a release without freezing develop
git switch -c release/2.0.0 develop

# patch production straight off main, then back-merge
git switch -c hotfix/login-crash main

Be honest about the trade: Git Flow does the most and is the heaviest of the four. The extra branches buy you parallel release management. If you don’t need that, they only buy you longer-lived branches and more merge surface. Most app teams do not need it.

Diagram: recommended branching strategy by project scale: commit-to-main when solo, GitHub Flow for small teams, trunk-based for continuous delivery, Git Flow for large scheduled releases. SOLO Commit to main tag your releases SMALL TEAM · 2-8 GitHub Flow recommended default GROWING · CD Trunk-Based short-lived branches LARGE · SCHEDULED Git Flow release + hotfix lanes fewer people · simpler more people · more process
The four strategies laid out along the axis that actually decides between them: how many people touch the code and how structured your releases must be.

Best for: Libraries, SDKs, enterprise software with scheduled, versioned, QA-gated releases.


Side-by-side

AttributeGitHub FlowTrunk-BasedGit Flow
Team size2-88-50+Any, with formal releases
Branch lifetimeHours to daysHoursDays to weeks
Long-lived branchesmain onlytrunk onlymain + develop
Release cadenceContinuousContinuous deploymentScheduled / versioned
CI requirementRecommendedEssential + feature flagsRecommended
ComplexityLowMedium (discipline)High

Which one are you in?

Pick your strategy

Solo projectjust me→ Commit to main, tag releases
Small team, ship oftencontinuous delivery→ GitHub Flow(start here if unsure)
Many committerstrunk must stay green→ Trunk-based + flags
Scheduled, versioned releasesseveral in flight→ Git Flow

When two answers fit, choose the simpler one. You can always add process later; you rarely regret removing it.

The honest meta-advice: teams reach for heavy strategies too early. Start with the lightest model your scale allows, and only add branches when a specific pain, such as parallel releases or an unprotectable trunk, forces your hand.

A strategy decides where code lives. The day-to-day habits that make it actually work (commit messages, pull requests, reviews, and how a branch lands) are in the companion piece: Git workflow for teams.

Cited sources