Branch, merge, and resolve a conflict live — the red text everyone panics at, fixed calmly.
The question every team fights about
You now know how to make a branch and how to guard main. The next question is one every team argues about, sometimes bitterly: how should branches be arranged over time? When do you cut one, how long does it live, and how does its work reach production? This is a branching strategy — a team's agreed convention for the shape of its history — and there is no shortage of dogma about it. The trap is treating it as a matter of taste or seniority. It is neither. It is a consequence of one number.1
- Branching strategy
- A team's shared rules for how branches are created, how long they live, and how their work is integrated and released. Not a Git feature — a social contract the tooling then enforces.
The number is deployment frequency: how often the team actually ships to production. A team that releases once a quarter has different needs from one that ships thirty times a day, and almost every honest disagreement about branching dissolves once you ask that question first. The two strategies this class compares — GitFlow and trunk-based development — are not good and evil; they are answers to different deployment frequencies, and choosing between them by fashion instead of frequency is how teams end up fighting their own process. Get this right and branching becomes invisible, the way it should be. Get it wrong and the strategy itself becomes a daily tax on everyone.
GitFlow: what it solved in 2010
In 2010 a developer named Vincent Driessen published a branching model that became so popular it got a name: GitFlow. To understand why it looks the way it does, remember the world it was born into. Software shipped on a schedule — versioned releases, every few weeks or months, often installed by customers rather than deployed by you. In that world you needed somewhere to stabilise the next release while ongoing work continued, and somewhere to patch the version already in the wild. GitFlow gave each of those a long-lived branch.
- main
- Holds only released, tagged versions — the history of what actually shipped.
- develop
- The long-lived integration branch where finished features accumulate between releases.
- feature/*
- One per feature, branched off develop and merged back when done — often living for weeks.
- release/* and hotfix/*
- A branch to stabilise the next version, and a branch to patch the current one in production.
For its moment, this was genuinely good engineering: it gave a versioned, ship-it-in-boxes product a clear place for every kind of work. But look at what it costs. Feature branches live long, so they drift far from develop and merge back in painful, conflict-heavy heaves. There are five kinds of branch to keep straight and a ceremony for moving between them. All of that friction is the price of decoupling "written" from "released" — and that price only makes sense if you release rarely. The moment your team wants to ship every day, GitFlow's careful machinery becomes the thing slowing you down.2
Trunk-based: the straight line most cloud teams run
The modern default for teams that deploy often is almost the opposite of GitFlow. In trunk-based development there is one long-lived branch — the trunk, usually main — and everyone integrates into it constantly. You still use branches, but they are short-lived: a branch cut this morning, opened as a PR, reviewed, and merged back the same day. Nothing is allowed to drift for weeks. The trunk is kept releasable at all times, and shipping is a matter of deploying the trunk, not of merging a pile of aged branches together.
Why does short-lived win for cloud teams? Because the pain of merging grows with the age of a branch, and trunk-based simply refuses to let branches get old. Small, frequent integrations mean small, frequent conflicts — the kind resolved in a minute, not a war. It leans hard on the machinery you already have: a protected trunk, required reviews, and automated checks on every PR, so that constant integration does not mean constant breakage. Feature flags do the rest, letting unfinished work sit merged-but-hidden on the trunk rather than marooned on a branch.3 The whole approach is built for the team that deploys many times a day and cannot afford a merge ceremony each time.
One more name belongs in your vocabulary, because interviewers ask for the three-way comparison. GitHub Flow is the middle strategy — the one the GitHub doc linked in the Watch panel describes: branch from main, open a pull request, merge, deploy, and nothing else — no develop branch, no release/* branches, no ceremony beyond the PR. It sits between this class's two poles: lighter than GitFlow by every measure, but more relaxed than strict trunk-based about how long a branch lives and how automatically the trunk deploys. When a team says "we just branch and PR," GitHub Flow is usually what they are describing without knowing its name.
Releases and environments
"Ship the trunk" raises an obvious question: ship it where, and how do you gain confidence before it reaches customers? The answer is not more branches — it is more environments. Instead of a develop branch to stabilise on, a modern team promotes the same build through a sequence of running environments, and the trunk stays the single source of truth.
| GitFlow-style | Trunk + environments | |
|---|---|---|
| Where work stabilises | On a long-lived branch (develop, release/*) | In running environments (dev → test → prod) |
| What differs between stages | Different branches of code | The same build, different config |
| How a fix reaches prod | Merge across branches, in order | Merge to trunk, promote the build forward |
| Failure mode | Branches drift; merges rot | A bad build is caught in an early environment |
The distinction to hold is promotion, not re-branching. A change merges once to the trunk, produces one artifact, and that same artifact is deployed to dev, then to a test or staging environment, then — behind an approval gate — to production. The code does not change as it moves; only the environment it runs in and the configuration it reads do. Some teams keep a thin release/* branch for a slower cadence or a supported version, and that is a legitimate hybrid — but the everyday flow is trunk to artifact to environments. This is exactly the shape Build III will construct: a pipeline that deploys the trunk to non-prod on merge and waits for a human to approve production. Environments, not branches, are where release confidence is earned.
The same machinery answers the question GitFlow reserved a whole branch type for: the production hotfix. In trunk-based there is no hotfix/*. A fix for a live incident is simply the next change — a short-lived branch off the trunk, a fast PR, a merge — followed by the ordinary promotion path run quickly: the new artifact clears dev and test in minutes and reaches production through the same approval gate, expedited rather than bypassed. The discipline matters more at two in the morning, not less; the fix that skips the pipeline is the fix that ships untested to a system that is already down. GitFlow patched the released version on its own branch and back-merged the patch everywhere; trunk-based fixes the trunk and promotes — one path, run faster.
Choose by frequency, not ideology
Now the decision, made honestly. You do not pick a branching strategy because it is "enterprise" or "what Google does" — you pick it by asking how often your team ships, then working backward.
Choose by frequency, not by fashion.
- Ship many times a day
- Trunk-based with short-lived branches and environment promotion. Long-lived branches would knot; you cannot afford a merge ceremony per release.
- Ship on a slow, versioned cadence
- A GitFlow-style model, or trunk plus a release/* branch, earns its overhead — you genuinely need to stabilise a version and patch old ones.
- Somewhere in between
- Most teams: trunk-based as the spine, with a light release branch only where a supported version demands it. Start simple; add branches only when a real need appears.
The failure mode to recognise in yourself and others is cargo-culting — adopting GitFlow's five-branch dance for a team that deploys daily, or forcing bleeding-edge trunk-based onto a team shipping firmware once a quarter. Both are the same mistake: choosing by identity instead of by deployment frequency. For most cloud infrastructure work — which is where you are headed — the honest answer in an interview is "trunk-based development with short-lived branches, protected trunk, and promotion through environments," because that is what deploying infrastructure many times a day actually requires. Know GitFlow well enough to explain what it solved and why it fell out of favour; that pairing — knowing both, and knowing why you chose one — is what separates an engineer from someone reciting a blog post.
Picking the flow the platform will live by
Campux's new lead, fresh from a big enterprise, opens with "we should run GitFlow — it is what serious companies use." You ask the question this class is built on: how often will we ship? The answer, for infrastructure that changes almost daily, settles it. GitFlow's develop and long release branches would have the team merging aged, conflicting changes into campux-platform constantly — the exact tangle Figure 11 circles in red. Campux commits to trunk-based: one protected main, branches that live hours not weeks, and every change reviewed on the way in.
Release confidence moves off branches and onto environments. The trunk builds one artifact; that artifact deploys to a non-prod environment on merge, and reaches production only behind a human approval. This is the shape Build III will implement for real — and it is a decision made on numbers, not on whose last employer did what. When the lead asks how they will support an older version if a big client ever pins one, you keep the door open: a thin release/* branch, added only if and when that need actually arrives.
The official module, and a CAMPUX overview
Design and implement branch strategies and workflows
learn.microsoft.com/training/modules/manage-git-branches-workflows/
GitHub Docs · Understanding the GitHub flow
docs.github.com/get-started/using-github/github-flow
A short walkthrough of GitFlow's tangle versus a trunk-based straight line, and how environments replace release branches, will live here. Video to be added.
Short-lived branch, in and out the same session
Feel the trunk-based loop at its natural speed: branch, change, PR, merge, delete — all in one sitting. The point is that the branch never gets old enough to drift.
Start from an up-to-date trunk, then cut a short-lived branch:
git switch main git pull git switch -c add-contributing
What just happened: you branched from the latest main. Because you pulled first, the branch starts life with zero drift — the ideal it will try to keep by not living long.Make one small change and push the branch:
printf "\n## Contributing\nBranch, PR, review, merge, delete.\n" >> README.md git commit -am "Document the contribution flow" git push -u origin add-contributing
On screen: GitHub prints a link to open a PR. One focused change on a fresh branch is exactly what a reviewer can read in minutes.Open the PR, let the checks and review pass, and merge it — then delete the branch when GitHub offers.
What to notice: the branch existed for minutes and is now gone. Its work lives on the trunk; the PR keeps the history. Nothing is left to rot.Bring your trunk up to date and confirm you are clean:
git switch main git pull git branch # the feature branch is gone locally too, once deleted
The lesson: that is the whole rhythm — start fresh, change small, integrate fast, delete. Repeat it many times a day and merges never become events.
Make a long-lived branch bite you
Now feel why trunk-based forbids old branches. You will let two branches edit the same lines while the trunk moves on, then watch the second merge collide — the pain that grows with a branch's age.
From an updated trunk, create two branches that will touch the same line:
git switch main && git pull git switch -c edit-title-a sed -i '1s/.*/# Campux Platform — A/' README.md git commit -am "Title edit A"
What just happened: branch A rewrote line one. Keep it unmerged for a moment — it is about to become the "old" branch that drifted.Make a competing change reach the trunk first, via its own branch and merge:
git switch main git switch -c edit-title-b sed -i '1s/.*/# Campux Platform — B/' README.md git commit -am "Title edit B" git switch main && git merge edit-title-b
On screen: the trunk now says "— B" on line one. Branch A still says "— A" and was cut before this landed. The two have diverged on the same line.Try to bring branch A up to date — and hit the conflict:
git switch edit-title-a git merge main
On screen: CONFLICT in README.md. Git cannot know whether line one should read A or B, so it stops and hands you both, wrapped in <<<<<<< markers.Resolve it by editing the file to the line you actually want, then finish the merge:
sed -i '1s/.*/# Campux Platform/' README.md # pick the real answer git add README.md git commit --no-edit
The lesson: this conflict took seconds because only one line diverged. Now imagine a branch that drifted for three weeks across forty files — the same mechanism, multiplied into an afternoon. That multiplication is exactly what short-lived branches exist to prevent. Delete these throwaway branches when done.
Zoom out: a branching model is a promise about how often you integrate pain
You can run trunk-based or a release-branch flow. Now reason about what the model does to the whole team, because a branch is deferred integration, and deferred pain compounds.
Long-lived branches defer merge pain, so they diverge, so the eventual merge is a weekend of conflicts nobody wants to own — which encourages even longer branches. What shrinks the batch back down?
main is the shared truth everyone depends on; a broken main blocks the whole team, so the model exists first to keep it releasable.
Integration frequency is the real lever; the strategy is just how you make small, frequent, reviewed merges the path of least resistance.
A model tuned for a library’s stability — release branches, careful merges — suffocates a web app that wants to ship daily, and the reverse is equally true.
A flow one team coordinates verbally needs written protection and automation before ten teams share the same main without treading on each other.
The engineer who ships is asked "did it branch clean?" The engineer who gets promoted is asked "and how long until it rejoins the truth?" — and kept the batch small.
How the team avoids a Friday
Two people are building on top of each other and stepping on toes. You untangle it with a branching strategy that fits the team — short-lived branches off main, merged small and often — so integration happens continuously instead of in one dreaded big-bang merge the night before release. The strategy is not dogma; it is how the team avoids a Friday.
Examination
Four drills, then two situations. The situations have no marking scheme — write your answer before you reveal the reasoning, or the exercise is worthless. Nothing is stored; this is between you and the page.
A. High deployment frequency is the fact that decides it: long-lived branches drift further the longer they live, so a team shipping daily under GitFlow spends its life reconciling aged branches. B chooses by prestige, not frequency — the exact cargo-cult this class warns against. C is GitFlow's failure mode taken to its extreme. D throws away review and protection entirely. Trunk-based keeps branches too young to drift, which is what shipping many times a day actually requires.
B. Trunk-based moves release confidence off branches and onto environments: the trunk produces one artifact, and that same artifact is promoted forward, changing only its configuration, until a human approves production. A and C are the branch-based habits it replaces; D is the dangerous version — building a different artifact per environment means you never actually tested what you shipped. Promotion, not re-branching, is the idea to hold — and it is the exact shape Build III implements.
Short-lived branches, feature flags for hidden work, and small frequent conflicts. The two false options are the common misreadings: trunk-based is not "commit to main with no review" — it leans harder on protection and checks than any other model, because constant integration would be constant breakage without them. And it does not remove the protected trunk; the protected trunk is precisely what makes it safe to integrate all day. Short branches plus strong guardrails, not a free-for-all.
FEATURE PLAN — as proposed
1. Cut a feature branch off main today
2. Build the whole feature over the next 6 weeks on it
3. Keep it off main until it is 100% finished and perfect
4. Rebase on main at the very end, then open one big PR
5. Merge the ~40-file branch in a single review
Line three. The six-week isolation is the decision every other problem flows from. While the branch sits off main, the trunk moves under it every day; by the end it has drifted so far that the "rebase at the very end" in line four is a minefield and the "single review" in line five is a rubber stamp on forty files nobody can hold in their head.
The fix is not a better merge — it is to never let the branch get old. Slice the feature into small pieces that each merge within a day, hidden behind a feature flag until the whole thing is ready. The work still takes six weeks; it just integrates continuously instead of colliding all at once. Branch age is the enemy, and line three is where the team chose to make it their enemy.
Argue on frequency, not identity. Do not say "GitFlow is old" or "trunk-based is better" — those are taste, and taste loses to a lead. Say the number: we deploy several times a day, and GitFlow's long-lived develop and release branches assume a slow, versioned cadence we do not have. Name the concrete cost — daily merges of drifting branches, the exact tangle we are trying to avoid — and offer the alternative in the same breath: trunk-based with a protected trunk and environment promotion, which gives the same safety GitFlow was reaching for, at our speed.
Bring evidence, not just opinion. Point at how the team actually ships today, and if you can, at what GitFlow would have cost on a recent change. The case is strongest when it is about this team's deployment frequency, not a blog post either of you read.
Then commit to the decision, whichever way it lands. If the lead still chooses GitFlow, you implement GitFlow well — disagreeing and committing is a senior trait, not a betrayal of your view. You made the argument on the merits; now the team needs one strategy executed cleanly, not two half-followed. Revisit it later with data if the pain you predicted shows up. Being right is worth less than being someone the team can decide with.
Concede the fear before you dissolve it. The worry is legitimate — unfinished work reaching customers would be bad. The move is to separate two things the objection has fused: being merged to main and being visible to a customer. Trunk-based keeps them apart deliberately, so "on main" never means "live to users."
Name the two mechanisms. First, feature flags: unfinished work merges to the trunk switched off, present in the code but dark to users until we choose to turn it on. Second, environments: the trunk deploys to dev and test long before production, and production sits behind an approval gate, so nothing reaches customers unreviewed. Merged, tested, and released are three different events.
Turn it back into the benefit. This is actually safer than the alternative: a giant feature branch merged after six weeks lands as one big, poorly understood change, while small flagged increments are each reviewed and integrated continuously. Customers see a feature the day we flip the flag — not a moment sooner — and by then it has been on the trunk, tested, the whole time. The stakeholder wanted safety; trunk-based with flags is how you give it to them.
Five things worth carrying out of this class
- A branching strategy is a consequence of one number — how often you ship — not a matter of taste or seniority.
- GitFlow's long-lived lanes (main, develop, feature, release, hotfix) suit a slow, versioned cadence; the price is drift and painful merges.
- Trunk-based keeps one protected trunk and short-lived branches, so integration is small and constant instead of large and dreaded — the modern default for cloud teams.
- Release confidence lives in environments, not branches: promote one build through dev → test → prod. Feature flags keep unfinished work merged but hidden.
- Choose by deployment frequency, not fashion. Know both strategies and why you picked one — that pairing is the interview answer.
- "Deployment frequency" is not a slogan — it is one of the four DORA metrics, the research-backed measures of software delivery performance (alongside lead time for changes, change failure rate, and time to restore service). The finding that elite teams both deploy far more often and fail less is what retired the old assumption that speed and safety trade off. Trunk-based development is strongly associated with the high-performing end of that data. Treat the exact rankings with a little suspicion — they are survey-based — but the direction is well replicated. ↩
- GitFlow's own author, Vincent Driessen, later added a note to his 2010 post urging readers not to treat it as a default — for teams practising continuous delivery on web apps, he now points them toward simpler, trunk-based flows. This is a healthy thing to cite in an interview: the model is not "wrong", it is a good answer to a question fewer teams are asking. Knowing that history signals you understand the why, not just the diagram. ↩
- "Trunk-based development" is sometimes used loosely to mean "commit straight to main", which is a caricature that gives it a bad name. The disciplined version — short-lived branches, a protected trunk, PR review, and automated checks on every change — is the one the research and this class mean. If someone dismisses trunk-based as reckless, they are usually arguing against the caricature; the real practice is more guarded than long-branch models, not less. ↩