Git branching strategies (Git Flow, GitHub Flow)?
Git branching strategies define structured rules for how a team creates, merges, and deletes branches to coordinate development without breaking production.
Theory
TL;DR
- Git Flow: factory assembly line with dedicated stations for features, testing, and fixes. GitHub Flow: food truck that cooks and serves immediately.
- Core split: Git Flow keeps a long-lived
developbranch as the integration point before production. GitHub Flow has nodevelopand merges directly tomain. - Scheduled versioned releases (desktop app, game)? Git Flow. Continuous web deploys? GitHub Flow.
- Team of 10+ with quarterly releases: Git Flow. Team of 1-10 shipping daily: GitHub Flow.
Quick example
# GitHub Flow: one short cycle, main stays deployable
git checkout main
git pull origin main # always start from latest
git checkout -b feature/user-login # short-lived branch
git add . && git commit -m "Add login form"
git push origin feature/user-login
# open PR, review, merge to main, delete branch
git branch -d feature/user-login
# main is deployable right nowOne branch. One PR. Main receives the change and is ready to deploy the same day.
Key difference
Git Flow keeps a develop branch as the permanent integration point. Features land on develop first. Then a release/* branch stages production-ready code. Only after that does main get a versioned tag like v1.2. GitHub Flow removes develop entirely: branch from main, open a pull request, merge back to main when approved. That is the whole structural difference. Git Flow is heavier but predictable for teams with fixed release schedules. GitHub Flow is faster for teams that deploy after every review.
When to use
- Mobile or desktop apps with quarterly releases: Git Flow handles versioning cleanly.
- Web apps or SaaS with daily deploys: GitHub Flow cuts branch overhead.
- Solo devs or small teams (1-5 people): GitHub Flow, no workflow tax.
- Enterprise with compliance audits: Git Flow gives a clean separation between production and staging.
- Monorepos with microservices: GitHub Flow plus tags and selective CI jobs per service.
Comparison table
| Aspect | Git Flow | GitHub Flow |
|---|---|---|
| Branches | main, develop, feature/, release/, hotfix/* | main, feature/* (short-lived) |
| Release cycle | Scheduled (v1.2 via release branch) | Continuous (merge to main = deploy) |
| Team size | Medium-large (10+ devs) | Small-agile (1-10 devs) |
| Tooling | git-flow extensions recommended | GitHub PRs + CI/CD |
| Merge frequency | Low (features collect in develop first) | High (daily PRs to main) |
| Best for | Electron, games, Chrome extensions | Next.js on Vercel, VS Code open-source |
How git handles branches internally
Git stores each branch as a lightweight pointer to a commit (a SHA-1 hash). Creating feature/login is just writing a small file under .git/refs/heads/. No code is copied. Merging finds the common ancestor via git merge-base and applies diffs from that point, either as a fast-forward or a merge commit. Git Flow adds CLI scripts (git flow feature start) that automate creation from develop. GitHub Flow skips those scripts and uses GitHub's pull request engine, which runs a server-side three-way merge.
The first time I ran a hotfix through full Git Flow, the auto-backport to develop saved a fix that would otherwise have been lost in the next release cycle. That said, most web teams since then use GitHub Flow and solve the same edge case with feature flags.
Common mistakes
Merging a feature directly to main in Git Flow
git checkout feature/add-api # branched from develop
git checkout main
git merge feature/add-api # skips develop, breaks integrationdevelop exists to catch integration bugs before they touch main. Skipping it breaks the whole model. Fix: use git flow feature finish add-api, which merges to develop automatically.
Keeping feature branches alive too long in GitHub Flow
git checkout -b feature/epic # created three weeks ago
git merge main # merge conflict explosionGitHub Flow requires short branches (1-3 days). If a feature takes weeks, split it into smaller PRs or use feature flags to hide incomplete work. Fix: git rebase main daily to stay current.
Skipping --no-ff on hotfix and release merges in Git Flow
git merge release/v1.0 # fast-forward erases the release step from historyWithout --no-ff, history looks linear and you lose the audit trail showing when a release was merged. Fix: git merge --no-ff release/v1.0.
Not deleting merged branches
Dead branches accumulate fast. A repo can hit thousands of stale refs after a year. Fix: enable auto-delete in GitHub PR settings and run git remote prune origin periodically.
Real-world usage
- React: GitHub Flow. Features branch from main, PRs with CI checks.
- VS Code: GitHub Flow. Daily main deploys to the Insiders build.
- Linux kernel: Git Flow variant.
mainlineacts as develop,stable/*as release branches. - Electron: Git Flow for tagged releases like v25.0.0.
- Kubernetes: trunk-based development (branches live less than a day).
Follow-up questions
Q: Walk through creating a hotfix in Git Flow.
A: Branch from main with git flow hotfix start fix-crash. Commit the fix. Run git flow hotfix finish fix-crash. This auto-merges to both main (tagged v1.2.1) and develop, so the fix is not lost in the next release cycle.
Q: Why not always use GitHub Flow?
A: It lacks a staging buffer for bigger releases. Without develop, a bad merge can break main before CI catches it. Git Flow gives you a place to catch integration bugs before they reach production.
Q: How do you handle versioned releases in GitHub Flow?
A: Merge to main and create a git tag (v1.2) immediately. CI/CD picks up the tag and deploys that commit. Semantic versioning still applies, you just skip the dedicated release branch.
Q: What is GitLab Flow and how does it differ?
A: GitLab Flow adds environment branches like staging and production between feature branches and main. Git Flow uses release branches tied to version numbers instead. Different mental model, similar goal.
Q: (Senior) In a monorepo with 50 services, how does GitHub Flow scale without breaking deploys?
A: Path filters in CI config (e.g., changed-files: ['services/auth/**']) trigger only the relevant jobs. Canary deploys and feature flags let you merge incomplete code without affecting users. Each service gets its own version tag.
Examples
Creating and merging a feature branch (GitHub Flow)
git checkout main
git pull origin main
git checkout -b feature/use-auth-hook
# src/hooks/useAuth.js
# export const useAuth = () => { ... checks token in localStorage ... }
git add src/hooks/useAuth.js
git commit -m "Add useAuth hook with token check"
git push origin feature/use-auth-hook
# open PR on GitHub, 2 reviewers approve
# auto-merge to main, CI runs tests green
# Vercel picks up main and deploys
git branch -d feature/use-auth-hookPR opened, reviewed, merged, deployed the same day. Main never had an unstable state.
Git Flow hotfix with backport to develop
# Production crash discovered during release/v1.2 prep
git checkout main
git checkout -b hotfix/critical-crash # branch from main, not develop
# fix the crash
git add . && git commit -m "Fix null pointer in auth handler"
git checkout main
git merge --no-ff hotfix/critical-crash
git tag -a v1.2.1 -m "Hotfix: null pointer in auth"
git checkout develop
git merge --no-ff hotfix/critical-crash # backport so fix lands in next release too
git branch -d hotfix/critical-crashmain gets tagged v1.2.1 and deploys. develop also carries the fix, so the next scheduled release does not reintroduce the bug.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.