Practical Git Workflows #1: Branching Strategies — GitHub Flow and Trunk-Based
In Git Basics #3: Branching and Merging — Fast-forward and 3-way we confirmed that creating a branch costs practically nothing, and in Git Basics #6: GitHub and Your First Pull Request we practiced landing a branch through a PR. Join a team, though, and you immediately run into the next questions. Where do branches start and what are they named, where do they merge, and at what point do you deploy? The team’s agreement on these questions is a branching strategy. This series is the follow-up to Git Basics — it covers not commands but the way teams run Git.
It runs in seven parts.
- #1 Branching strategies — GitHub Flow and trunk-based ← this post
- #2 rebase vs merge — decision criteria and the golden rule
- #3 Interactive rebase — cleaning up commits with squash and fixup
- #4 Resolving conflicts — structure, mergetool, and rerere
- #5 Running Pull Requests — review size, commit messages, and draft PRs
- #6 stash, cherry-pick, bisect — everyday tools
- #7 Monorepos and Git — sparse-checkout, submodules, and LFS
This post walks through the three representative strategies you will meet in practice — Git Flow, GitHub Flow, and trunk-based development — and closes with a table of selection criteria.
What happens without a strategy #
Start creating branches without a strategy and the same problems repeat within days. The code you are about to deploy contains changes nobody has verified, no one can answer where an urgent bug fix should branch from, and long-lived branches drift so far from main that every merge turns into a large conflict.
A strategy answers three questions.
- Where do branches start — the branch that new work branches off from
- Where do they merge — the merge target for finished work
- What gets deployed — the branch whose code goes to production
The three strategies answer these questions differently.
Git Flow — five kinds of branches #
Git Flow, proposed in 2010, is the oldest and most structured strategy. It divides branches into five kinds.
main ──●─────────────────●────────▶ tagged per release (v1.0, v1.1)
\ /
release \ ●──● release prep, bug fixes only
\ /
develop ──●───●──●──●───●───●──▶ integration line toward the next release
\ / \ /
feature ●──● ●───● work in feature units
hotfix: branches off main, merges into both main and develop- main — only released code lives here. Every commit gets a version tag.
- develop — the integration branch where features gather toward the next release.
- feature — branches off develop and returns to develop.
- release — a stabilization branch right before a release. Accepts bug fixes only.
- hotfix — for urgent production fixes; branches off main and merges into both main and develop.
This structure was designed around software that cuts a versioned release every few months. For installable products and mobile apps — environments where a release is an event — it still fits well. For a web service that deploys several times a day, develop and release tend to become little more than formalities, leaving only the cost of managing branches. Ten years later, the original author himself added a retrospective note recommending a simpler flow for continuously delivered web services.
GitHub Flow — main and short-lived branches #
GitHub Flow strips the structure down to the extreme. There are only two kinds of branches.
main ──●────────●────────●──▶ always kept deployable
\ / /
feature ●──● ●──● short-lived work branches, landed via PRThe rules are short as well.
- main is always kept in a deployable state.
- Work happens on a named branch created off main.
- Open a PR, get a review, and merge into main when it passes.
- Deploy the merged main immediately, or as soon as possible.
As you may have noticed, the cycle you practiced in Git Basics #6 — create a branch, push, open a PR, merge — is exactly GitHub Flow. Because it costs nothing extra to learn and connects directly to a PR review culture, it has become the de facto default for continuously deployed web services. There is no develop and no release, so questions like “what goes into the next release” are answered not by branches but by PRs and deployment records.
Trunk-based development — straight to main #
Trunk-based development goes one step further. All work is committed directly to main (the trunk), or on branches so short-lived that they merge within a day or two.
main ──●──●──●──●──●──●──●──▶ everyone integrates into main daily
\ /
very short branch ●● lifespan within 1–2 daysThe core goal is eliminating integration delay. The longer a branch lives, the further it drifts from main and the larger the conflicts grow; integrate daily and conflicts get resolved while they are still small. In exchange, the prerequisites are heavy.
- Solid automated tests and CI — broken code must be unable to enter main even without a human gatekeeper.
- Feature flags — unfinished features are merged into main but switched off in code. The technique separates deployment from release: an unfinished feature can sit in main without being visible to users. Turning it on is a matter of configuration, not deployment.
A team that copies the form without these two in place ends up in the worst state of all: everyone sharing a broken main. Organizations like Google and Meta can work this way because that much infrastructure backs it.
Selection criteria #
Here are the three strategies organized along decision axes.
| Criterion | Git Flow | GitHub Flow | Trunk-based |
|---|---|---|---|
| Release model it fits | versioned releases (installable, mobile) | continuously deployed web services | multiple deploys per day |
| Branch lifespan | long (weeks) | short (days) | very short (1–2 days) |
| Maintaining multiple versions | strong | weak | weak |
| Prerequisites | release management process | CI and review culture | strong CI, feature flags |
| Structural complexity | high | low | low (but demanding to operate) |
When you need to decide, ask two questions. First, is a release an event or an everyday occurrence? If you cut versions and also maintain older ones, the Git Flow family is still valid. Second, do you have CI that can absorb daily integration? If so, the shorter you make branch lifespans — that is, the closer you move toward trunk-based — the more you gain.
A strategy is an agreement, not a tool #
For most teams the safe starting point is GitHub Flow. It has few rules, combines naturally with a PR review culture, and can be adjusted toward trunk-based when the need arises. Whichever strategy you pick, what matters is that the whole team shares the same rules. A team where half merges into develop and half into main will not converge under any strategy. Write the strategy down, even briefly, in the repository’s CONTRIBUTING document, so a new teammate can find the answer before creating their first branch.
Wrap-up #
Three takeaways from this post.
- A branching strategy is the team’s agreement on where branches start, where they merge, and what gets deployed.
- Git Flow fits versioned-release products, GitHub Flow fits continuously deployed web services, and trunk-based fits high-frequency deploy organizations with strong CI.
- GitHub Flow is the safe default, and shortening branch lifespans moves you toward trunk-based.
In the next post, “Practical Git Workflows #2: rebase vs merge — Decision Criteria and the Golden Rule”, we cover rebase, the second way to bring diverged histories together — how it differs from merge, and the one situation where you must never rebase.