Skip to main content

🌿 Branching Strategies and Workflows

Branching is the feature that made Git famous. In a few milliseconds you can spin off a parallel line of development, experiment freely, and either fold your work back in or throw it away — all without touching the code your teammates depend on. This lesson takes you from "what is a branch, really?" to the four workflows real teams argue about.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Explain what a Git branch actually is under the hood — a movable pointer — and the role of the HEAD pointer
  • Perform every core branching operation: create, list, switch, merge, rename, and delete
  • Compare the four dominant workflows — GitFlow, GitHub Flow, Trunk-Based Development, and Release Flow
  • Choose an appropriate strategy for a given team, release cadence, and risk profile
  • Apply naming conventions and branch-protection rules that keep a shared repository healthy

Estimated Time: 35–45 minutes  •  Difficulty: Intermediate

Hands-on: Build a two-feature history from scratch and merge both branches into main.

In This Lesson

Why Branching Matters

In the previous lesson you learned Git's core commands — add, commit, status, log. Those let you record history on a single line of development. But real projects are never a single line. Two people fix two different bugs at once; you want to try a risky rewrite without endangering the version that ships tomorrow; a hotfix has to go out while a big feature is half-finished. Branching is how Git handles all of that gracefully.

💡 A useful analogy: A branch is a parallel universe for your code. You step into it, make whatever changes you like, and the "main timeline" is completely untouched until you deliberately merge the two universes back together. If the experiment fails, you delete the universe and no one ever knows it existed.

Here's the simplest possible branch-and-merge story: split off a feature branch, do some work, and bring it home.

gitGraph commit commit branch feature checkout feature commit commit checkout main commit merge feature commit

The reason Git can do this so cheaply — and why it changed how the industry works — is that a branch is almost nothing. Let's see exactly what.

What a Branch Really Is

In many older version-control systems, "branching" meant copying every file into a new folder — slow and heavy. In Git, a branch is just a 40-byte file containing the hash of one commit. Creating a branch creates that tiny pointer and nothing else. That's why it's instant.

Think of a branch as a sticky note placed on a specific commit. When you make a new commit while standing on that branch, Git peels the sticky note off the old commit and moves it forward to the new one — automatically.

Branch pointers and the HEAD pointer A chain of four commits. The main branch pointer sits on the third commit, a feature branch pointer sits on the fourth, and HEAD points at the feature branch. C1 C2 C3 C4 main feature HEAD
Figure 1 — Branches are pointers to commits. HEAD is a pointer to a branch, telling Git "you are here." Committing moves whichever branch HEAD points at.

The HEAD pointer

Alongside your branch pointers, Git keeps one special pointer called HEAD. HEAD answers the question "which branch am I currently on?" When you check out a branch, HEAD attaches to it; when you commit, the branch HEAD points to moves forward.

If you check out a raw commit hash instead of a branch, HEAD points directly at that commit — a state Git calls detached HEAD. New commits made there belong to no branch and can be garbage-collected later, so if you want to keep them, create a branch first.

📖 Key Terms

Branch: a lightweight, movable pointer to a commit.

HEAD: a pointer to the branch you currently have checked out.

Detached HEAD: the state where HEAD points at a commit directly, not a branch — useful for looking around, dangerous for committing.

Core Branching Commands

Modern Git (2.23 and newer) split the overloaded git checkout into two clearer verbs: git switch for moving between branches and git restore for discarding file changes. Both the old and new commands still work; we'll show the modern form first and note the classic equivalent.

Create, list, and switch

# Create a branch (does NOT move you onto it)
git branch feature-login

# List branches — the current one is marked with *
git branch          # local branches
git branch -r       # remote-tracking branches
git branch -a       # all branches, local and remote

# Switch onto a branch
git switch feature-login      # modern
git checkout feature-login    # classic equivalent

# Create AND switch in one step
git switch -c feature-login   # modern
git checkout -b feature-login # classic equivalent

Rename, merge, and delete

# Rename the branch you are currently on
git branch -m new-name

# Rename a branch you are NOT on
git branch -m old-name new-name

# Merge another branch INTO your current branch
git switch main
git merge feature-login

# Delete a branch (safe: refuses if it has unmerged work)
git branch -d feature-login

# Force-delete, even with unmerged work (be sure!)
git branch -D feature-login

⚠️ Watch the direction of a merge

git merge always merges the named branch into the branch you are standing on. Before merging, run git status to confirm HEAD is on the target (usually main). Merging into the wrong branch is one of the most common beginner mistakes.

The Four Workflows

The mechanics above are universal. What differs between teams is the convention — which branches exist, how long they live, and how work flows between them. These conventions are called branching strategies. Here are the four you will meet most often.

1. GitFlow

Introduced by Vincent Driessen in 2010, GitFlow is a structured model organized around scheduled releases. It uses long-lived main and develop branches plus short-lived feature/*, release/*, and hotfix/* branches.

gitGraph commit branch develop checkout develop commit branch feature/login checkout feature/login commit commit checkout develop merge feature/login branch release/1.0 checkout release/1.0 commit checkout main merge release/1.0 checkout develop merge release/1.0
BranchPurpose
mainAlways production-ready; every commit is a shipped version
developIntegration branch — the staging area for the next release
feature/*Branch off develop, merge back into develop
release/*Final polish and bug-fixing before a release; merges into both main and develop
hotfix/*Emergency fix branched off main; merges back into both main and develop

Strengths: clear roles, strong support for parallel features and multiple maintained versions. Weaknesses: heavy for small teams, slow to integrate, and poorly suited to continuous delivery — main can drift far behind reality between releases.

# Start and finish a feature under GitFlow
git switch develop
git switch -c feature/user-login
# ...work and commit...
git switch develop
git merge feature/user-login
git branch -d feature/user-login

2. GitHub Flow

A radically simpler model built for continuous delivery. There is one long-lived branch — main, always deployable — and short-lived branches off it, each reviewed via a pull request and deployed the moment it merges.

gitGraph commit branch feature-a checkout feature-a commit commit checkout main merge feature-a branch feature-b checkout feature-b commit checkout main merge feature-b

The whole workflow is: branch off main → commit and push → open a pull request → review and run CI → merge → deploy immediately. Strengths: dead simple, fast iteration, few conflicts thanks to tiny branches. Weaknesses: little built-in support for maintaining several released versions, and it leans hard on solid automated testing and deployment.

git switch main
git pull
git switch -c feature/add-login
# ...work...
git add .
git commit -m "Add login form"
git push -u origin feature/add-login
# open a pull request, get it reviewed, merge, deploy

3. Trunk-Based Development

Here everyone integrates into a single branch ("the trunk", usually main) many times a day. Branches, if used at all, live only hours. Incomplete work hides behind feature flags so main stays releasable at all times.

gitGraph commit branch feature-quick checkout feature-quick commit checkout main merge feature-quick commit branch bugfix checkout bugfix commit checkout main merge bugfix commit

Strengths: minimal branch management, tiny and rare conflicts, and it's the foundation of true continuous integration. Weaknesses: demands excellent automated test coverage and disciplined developers; feature-flag sprawl can become its own maintenance burden.

4. Release Flow (Microsoft's model)

A pragmatic middle ground that Microsoft's engineering teams adopted. Day-to-day work looks like GitHub Flow — feature branches off a healthy main, merged via pull requests. When it's time to ship, a release/* branch is cut from main; fixes needed on that release are made there and cherry-picked back to main.

gitGraph commit branch feature/login checkout feature/login commit checkout main merge feature/login branch release/1.0 checkout release/1.0 commit checkout main commit

Strengths: combines GitHub Flow's simplicity with GitFlow's ability to support scheduled releases and multiple live versions. Weaknesses: cherry-picking is error-prone and needs discipline and good tooling.

✅ The pattern behind all four

Every strategy answers the same three questions differently: which branches live long?, where does new work start?, and how do fixes reach released versions? Once you can answer those for a strategy, you understand it.

Choosing a Strategy

There is no single "best" workflow — only the best fit for your team, cadence, and risk. Weigh these factors:

  • Team size & distribution — larger, more distributed teams benefit from more structure.
  • Release cadence — deploying many times a day pushes you toward GitHub Flow or Trunk-Based; scheduled quarterly releases suit GitFlow or Release Flow.
  • Number of maintained versions — supporting several live versions needs release branches.
  • Test automation maturity — Trunk-Based Development is only safe with strong automated tests.
  • Regulatory / audit needs — heavily regulated industries value the explicit trail that GitFlow and Release Flow provide.
flowchart TD A[Need to support multiple released versions?] -->|Yes| B[Scheduled, batched releases?] A -->|No| C[Practicing continuous delivery?] B -->|Yes| D[GitFlow] B -->|No| E[Release Flow] C -->|Yes| F[Strong automated tests?] C -->|No| G[GitHub Flow] F -->|Yes| H[Trunk-Based Development] F -->|No| G
ContextRecommendedWhy
Startup / small teamGitHub Flow or Trunk-BasedLow overhead, fast iteration, easy communication
Enterprise product, batched releasesGitFlow or Release FlowStructure for multiple versions and audit trails
Web app, continuous deliveryGitHub Flow or Trunk-BasedRapid, frequent deployment
Open-source projectGitHub Flow (or GitFlow if complex)Clear structure for outside contributors

These are templates, not laws. Plenty of teams run hybrids — GitHub Flow plus occasional release branches for major versions, or Trunk-Based with slightly longer branches for big changes. The single most important thing is that the whole team follows the same convention consistently.

Naming & Branch Protection

Naming conventions

A consistent prefix tells everyone a branch's purpose at a glance. Use kebab-case after the prefix:

PrefixUse forExample
feature/New functionalityfeature/user-authentication
bugfix/Non-critical fixesbugfix/login-validation
hotfix/Urgent production fixeshotfix/payment-timeout
release/Release preparationrelease/1.4.0
docs/Documentation onlydocs/api-reference

Branch protection

Hosting platforms (GitHub, GitLab, Bitbucket) let you enforce your workflow with branch protection rules on important branches like main:

  • Require pull-request review before merging (block direct pushes)
  • Require status checks — tests and CI must pass first
  • Restrict who may push
  • Require a linear history (force rebase/squash over merge commits)
  • Automatically delete branches once merged

Set up the modern defaults when you start a repo:

# New repos default to a 'main' branch (Git 2.28+)
git config --global init.defaultBranch main

# Initialize a repo with main directly
git init -b main

# Prune remote-tracking branches that were deleted upstream
git fetch --prune

Hands-on Exercise

🏋️ Build a Two-Feature History

Objective: Create two feature branches from main, develop them independently, and merge both back — then read the graph you produced.

Instructions:

  1. Create an empty repository and an index.html, then commit it to main.
  2. Create and switch to feature/header; add a header element and commit.
  3. Switch back to main, then create feature/footer; add a footer (a different part of the file so it won't conflict) and commit.
  4. Switch to main and merge feature/header, then merge feature/footer.
  5. Run git log --graph --oneline --all and describe the shape you see.
💡 Hint

Because the two features touch different regions of the file, the first merge is a fast-forward and the second is a true (three-way) merge that creates a merge commit. Watch how the graph branches then rejoins.

✅ Solution
mkdir branch-demo && cd branch-demo
git init -b main
printf '<body>\n</body>\n' > index.html
git add index.html
git commit -m "Add empty page skeleton"

git switch -c feature/header
# insert <header>Site Title</header> near the top of index.html
git commit -am "Add page header"

git switch main
git switch -c feature/footer
# insert <footer>© 2026</footer> near the bottom of index.html
git commit -am "Add page footer"

git switch main
git merge feature/header    # fast-forward
git merge feature/footer    # creates a merge commit
git log --graph --oneline --all

The final graph shows main advancing straight through the header commit, then splitting to the footer branch and rejoining at a merge commit.

🎯 Quick Quiz

Question 1: Under the hood, what is a Git branch?

Question 2: Which workflow keeps one always-deployable main branch and ships each pull request the moment it merges?

Question 3: You run git switch main then git merge feature. What happens?

Best Practices

✅ Do

  • Keep branches focused and short-lived — one clear purpose, merged within days.
  • Commit often with meaningful messages; consider Conventional Commits.
  • Sync with main regularly so conflicts stay small.
  • Use pull requests for review and enforce branch protection on shared branches.
  • Delete branches after merging and prune remotes to keep the repo tidy.

⚠️ Don't

  • Let a "development-hell" branch live for months with hundreds of commits — it becomes a merge nightmare.
  • Force-push over shared branches; if you must, use --force-with-lease so you never clobber a teammate's work.
  • Mix unrelated changes in one branch — it makes review and revert harder.
  • Adopt a strategy no one wrote down; document it in CONTRIBUTING.md.

Example of a well-formed commit message that pairs beautifully with any branching strategy:

feat(auth): add password-reset flow

Send a time-limited token by email and validate it when the user
clicks the reset link, so users can recover accounts without support.

Resolves: #123

Summary & Quiz

🎉 Key Takeaways

  • A branch is a movable pointer to a commit; HEAD points at the branch you're on. That's why branching is instant.
  • The core verbs are git switch/checkout, git branch, and git merge; merge pulls the named branch into your current one.
  • The four dominant workflows — GitFlow, GitHub Flow, Trunk-Based, Release Flow — differ only in which branches live long, where work starts, and how fixes reach releases.
  • Choose by team size, release cadence, versions maintained, and test maturity — then document and apply it consistently.
  • Naming conventions and branch protection keep shared repositories healthy.

📚 Further Reading

🚀 What's Next?

You can now create branches and pick a workflow. Next we go deep on the moment two branches come back together: the mechanics of merging vs. rebasing, and how to resolve the conflicts that inevitably arise.

🎉 Nice work!

You've mastered how work diverges. Time to learn how it converges again.