Traffic rules for code: Branching strategies are like traffic rules for a busy highway — they decide when lanes split, how they merge back, and who has right of way. Without rules, you get collisions (merge conflicts) and gridlock (delayed releases).
Why it matters: The right branching strategy determines how quickly your team can ship features, how easy it is to roll back bugs, and how much time you spend resolving conflicts instead of writing code.
The key insight: There is no universally “best” strategy — the right choice depends on your team size, release cadence, and risk tolerance. A startup needs speed; a bank needs safety.
A branching strategy defines when to create branches, how long they live, how they integrate, and Who can modify which branches . There is no universal “best” strategy — the right choice depends on Team size, release cadence, deployment model, and risk tolerance.
This guide covers the most widely-used strategies, their trade-offs, and when to use each.
All developers commit to a single shared branch ( main). Feature branches are extremely Short-lived (hours, not days). Integration is continuous — every commit is potentially deployable.
gitGraph
commit id: "A"
commit id: "B (dev 1)"
commit id: "C (dev 2)"
commit id: "D (dev 1)"
commit id: "E (dev 3)"
commit id: "F (CI/CD deploys every commit)" No long-lived branches . Feature branches exist for at most a few hours.Continuous integration . Every commit triggers automated tests.Feature flags . Incomplete features are hidden behind configuration toggles.Small batches . Commits should be small and independently reviewable.Pre-merge validation . Automated tests must pass before merging.Advantage Explanation No merge hell No large, complex merges — changes are integrated incrementally Fast feedback CI runs on every commit, catching issues immediately Easy rollback git revert <hash> undoes a single commitSimplified workflow No branch management overhead
Disadvantage Mitigation Requires robust CI/CD Invest in automated testing before adopting Feature flags add complexity Use a feature flag management system Large teams may have contention Use short-lived feature branches (< 1 day) Requires disciplined commits Commit frequently, keep commits atomic
Teams with strong CI/CD pipelines Continuous deployment environments Small to medium teams (up to ∼ \sim ∼ 15 developers) SaaS products with frequent releases Well with proper tooling (Bazel for builds, automated canary deployments).A simplified version of trunk-based development with one long-lived branch (main) and short-lived Feature branches. Every change requires a pull request.
gitGraph
commit id: "A"
commit id: "B"
branch feature-auth
checkout feature-auth
commit id: "C"
commit id: "D"
checkout main
commit id: "E"
checkout feature-auth
merge main id: "F (merge)"
checkout main
merge feature-auth id: "G (merge commit)"
commit id: "H" Create a branch from main: git switch -c feature-auth Make commits and push: git push -u origin feature-auth Open a pull request Discuss, review, and iterate Merge into main (with --no-ff) Deploy main main is always deployable.All changes go through pull requests. PRs should be small (ideally < 400 lines changed). Tests must pass before merging. Deploy main after every merge (or on a schedule). Open-source projects Teams without formal release cycles Projects with continuous deployment Any team using GitHub A structured branching model with two long-lived branches (main and develop) and several Short-lived branch types. Originally published by Vincent Driessen in 2010.
flowchart TD
subgraph "Long-lived branches"
MAIN["main<br/>(production-ready)"]
DEV["develop<br/>(integration)"]
end
subgraph "Short-lived branches"
F["feature/*<br/>(new features)"]
R["release/*<br/>(release prep)"]
H["hotfix/*<br/>(urgent fixes)"]
end
MAIN ---|"merge (no-ff)"| DEV
DEV ---|"branch"| F
F ---|"merge (no-ff)"| DEV
DEV ---|"branch"| R
R ---|"merge (no-ff)"| MAIN
R ---|"merge (no-ff)"| DEV
MAIN ---|"branch"| H
H ---|"merge (no-ff)"| MAIN
H ---|"merge (no-ff)"| DEV
style MAIN fill:#ffcdd2
style DEV fill:#e8f5e9
style F fill:#e3f2fd
style R fill:#fff3e0
style H fill:#f3e5f5 Branch Purpose Lifetime Created From Merges Into mainProduction releases Permanent — — developIntegration branch Permanent main— feature/*Feature development Short developdeveloprelease/*Release preparation Short developmain + develophotfix/*Urgent production fixes Short mainmain + develop
$ git switch -c feature/login develop
$ git merge --no-ff feature/login
$ git branch -d feature/login
$ git switch -c release/1.0 develop
# ... bump versions, fix bugs, update docs ...
$ git merge --no-ff release/1.0
$ git merge --no-ff release/1.0
$ git branch -d release/1.0
$ git switch -c hotfix/fix-crash main
$ git merge --no-ff hotfix/fix-crash
$ git merge --no-ff hotfix/fix-crash
$ git branch -d hotfix/fix-crash
Advantage Explanation Clear separation Features, releases, and hotfixes have distinct branches Parallel development Multiple features can be developed simultaneously Release isolation Release branches allow bug fixes without blocking features Well-documented Widely understood, many tools support it natively
Disadvantage Explanation Complex 5 branch types, strict merge rules — high cognitive overhead Merge-heavy Every feature requires a merge commit into developThen another into main Slow feedback Features can live in isolation for weeks, accumulating conflicts Not ideal for CI/CD The develop branch creates an unnecessary integration step
Projects with scheduled releases (e.g., monthly, quarterly) Teams that need to support multiple production versions simultaneously Projects where releases require significant preparation (version bumps, changelogs, release notes) Regulated environments where release audit trails are required Development is simpler and more effective. Only adopt Git Flow if you genuinely need release Branches and hotfix workflows.Each developer has their own fork (personal copy) of the canonical repository. Changes flow from Fork → pull request → canonical repository.
flowchart LR
subgraph "Developer A"
FA["fork/user-a/repo"]
end
subgraph "Developer B"
FB["fork/user-b/repo"]
end
subgraph "Canonical"
UP["org/repo"]
end
FA -- "PR" --> UP
FB -- "PR" --> UP
UP -- "git fetch" --> FA
UP -- "git fetch" --> FB Open-source projects (contributors don”t have write access) External contractors Organizations where write access is restricted Criterion Trunk-Based GitHub Flow Git Flow Complexity Low Low High Branch count 1 + ephemeral 1 + short-lived 2 + multiple types Merge frequency Continuous Per PR Per feature/release CI/CD requirement Mandatory Strongly recommended Recommended Release model Continuous On-demand Scheduled Hotfix handling git revertBranch from main Dedicated hotfix/* branch History cleanliness Linear Mostly linear Complex merge graph Team size Small–medium Any Any Learning curve Low Low Moderate
Regardless of branching strategy, consistent commit messages are essential. The most widely-used Convention is Conventional Commits :
<type>(<scope>): <description>
Type Purpose featNew feature fixBug fix docsDocumentation changes styleFormatting, whitespace (no code change) refactorCode restructuring without behavior change perfPerformance improvement testAdding or updating tests choreBuild process, dependencies, tooling ciCI/CD configuration changes
Examples:
feat(auth): add JWT token refresh
Implement automatic token refresh when the access token expires.
Uses a refresh token stored in an HTTP-only cookie.
fix(parser): handle empty input without crashing
The parser would segfault on empty input because it dereferenced
a null pointer after strtok returned NULL. Added a null check.
BREAKING CHANGE: The parser now returns an error instead of silently
accepting empty input. Update callers to handle ParseError.
In CI:npm install --save-dev @commitlint/cli @commitlint/config-conventional
echo " export default { extends: ['@commitlint/config-conventional'] }; " > commitlint.config.js
Confusing authentication (who you are) with authorisation (what you can do) in security contexts.
Neglecting to normalise database designs, leading to data redundancy and update anomalies.
Forgetting that O ( n log n ) O(n \log n) O ( n log n ) average-case for quicksort becomes O ( n 2 ) O(n^2) O ( n 2 ) worst-case on already sorted input.
Forgetting edge cases in algorithm design (e.g., empty input, single element, already sorted data).
The key principles covered in this topic are linked in the sub-pages above. Focus on understanding the definitions, applying the formulas or frameworks, and evaluating strengths and limitations of each approach.
Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.
---
Remote Operations provides the fundamental fetch, pull, and push commands that workflows are built upon.Pull Requests implements the collaborative review process that many workflows centre around.Branching defines the branching strategies (Git flow, GitHub flow) that structure team workflows.