Git Branch Basics: Creating, Switching, Listing, and Deleting Branches
Master the fundamentals of Git branching — creating, switching, listing, and deleting branches. Learn the core commands that enable parallel development workflows.
Introduction
Branching is the superpower that separates Git from centralized version control systems. In Git, branches are lightweight, fast to create, and designed to be used liberally. Every commit you make lives on a branch, and the default main branch is no different from any branch you create yourself.
Understanding branch fundamentals is the gateway to effective parallel development. Whether you’re isolating a feature, fixing a bug, or experimenting with a refactor, branches let you work without disrupting the main codebase. This guide covers every essential branch operation you’ll need in daily development.
Branches in Git are simply pointers to commits. When you create a new branch, Git creates a new pointer — it doesn’t copy files or duplicate history. This is why branching is nearly instantaneous, even in repositories with hundreds of thousands of commits.
When to Use / When Not to Use
When to Use Branches
- Feature development — isolate new functionality until it’s ready for integration
- Bug fixes — patch production issues without pulling in unfinished work
- Experiments — try radical changes with zero risk to stable code
- Release management — stabilize a release while main continues to evolve
- Code reviews — each branch becomes a natural unit for pull request review
When Not to Use Branches
- Trivial one-line changes — commit directly to main for typo fixes or documentation tweaks
- Long-lived branches — branches that live for months accumulate drift and merge pain
- Personal preference divergence — don’t branch for formatting or style changes that create noise
- Avoiding communication — branches shouldn’t replace talking to your team about integration plans
Core Concepts
A Git branch is a movable pointer to a commit. The default branch is typically named main (or master in older repositories). Git tracks the current branch with a special pointer called HEAD.
commit A ── commit B ── commit C ← main (HEAD)
When you create a new branch, Git creates a new pointer at the current commit:
commit A ── commit B ── commit C ← main
└────── feature ← HEAD
As you commit on the new branch, only that branch pointer advances:
commit A ── commit B ── commit C ← main
└── commit D ── commit E ← feature (HEAD)
graph LR
A["commit A"] --> B["commit B"]
B --> C["commit C"]
C --> D["commit D"]
D --> E["commit E"]
C -. "main" .-> C
E -. "feature (HEAD)" .-> E
Architecture or Flow Diagram
flowchart TD
Start["Start on main"] --> Create["git branch feature-x"]
Create --> Switch["git switch feature-x"]
Switch --> Work["Make changes and commit"]
Work --> Check{"Need another branch?"}
Check -->|Yes| SwitchBack["git switch main"]
SwitchBack --> Create2["git branch feature-y"]
Create2 --> Switch2["git switch feature-y"]
Switch2 --> Work2["Work on feature-y"]
Work2 --> List["git branch -a"]
Check -->|No| List
List --> Delete["git branch -d feature-x"]
Delete --> End["Clean branch state"]
Step-by-Step Guide / Deep Dive
Listing Branches
# List local branches (current branch marked with *)
git branch
# List all branches including remote tracking branches
git branch -a
# List remote branches only
git branch -r
# Show last commit on each branch
git branch -v
# Show branches merged into current branch
git branch --merged
# Show branches NOT merged into current branch
git branch --no-merged
Creating Branches
# Create a new branch from current HEAD
git branch feature-x
# Create a branch from a specific commit
git branch feature-x abc1234
# Create a branch from another branch
git branch feature-x main
# Create and switch in one command (most common)
git switch -c feature-x
# Legacy equivalent (still widely used)
git checkout -b feature-x
Switching Branches
# Switch to an existing branch
git switch feature-x
# Switch to main
git switch main
# Switch to previous branch (like cd -)
git switch -
# Switch and create if it doesn't exist
git switch -c new-branch
# Switch and discard local changes
git switch --discard-changes feature-x
Deleting Branches
# Delete a merged branch (safe — refuses if unmerged)
git branch -d feature-x
# Force delete an unmerged branch (dangerous)
git branch -D feature-x
# Delete a remote branch
git push origin --delete feature-x
# Prune local tracking branches that no longer exist on remote
git remote prune origin
Branch Naming Conventions
# Good naming patterns
git switch -c feature/user-authentication
git switch -c fix/login-timeout-bug
git switch -c hotfix/security-patch
git switch -c release/v2.1.0
git switch -c refactor/database-layer
# Avoid these
git switch -c my-branch # too vague
git switch -c fix # what are you fixing?
git switch -c temp # will be forgotten
git switch -c feature/FEAT-123 # redundant prefix
Production Failure Scenarios + Mitigations
| Scenario | Impact | Mitigation |
|---|---|---|
| Accidentally delete unmerged branch | Lost work | Use git branch -d (not -D); enable git reflog recovery |
| Switch with uncommitted changes | Work carried to wrong branch | Use git stash before switching, or git switch --discard-changes intentionally |
| Branch name collision with remote | Push/pull confusion | Use unique naming conventions; verify with git branch -a |
| Orphaned branches accumulate | Repository clutter | Regular git branch --merged cleanup; automate with CI |
| HEAD detached state | Confusion about where commits go | Use git switch -c recovery-branch to rescue detached commits |
Recovery: Deleted Branch
# Find the commit SHA from reflog
git reflog
# Recreate the branch at that commit
git branch recovered-branch <sha>
Trade-offs
| Approach | Pros | Cons |
|---|---|---|
git branch + git switch | Explicit, clear intent | Two commands instead of one |
git checkout -b | Single command | Overloads checkout with branch creation |
| Short-lived branches | Easy to merge, minimal drift | Requires discipline to clean up |
| Long-lived branches | Stable integration target | Merge conflicts accumulate, diverges from main |
| Descriptive names | Self-documenting, searchable | Longer to type |
| Short names | Quick to type | Ambiguous, hard to track purpose |
Implementation Snippets
# Complete feature branch workflow
git switch main
git pull origin main
git switch -c feature/payment-integration
# ... work, commit, test ...
git push -u origin feature/payment-integration
# Cleanup after merge
git switch main
git pull origin main
git branch -d feature/payment-integration
git push origin --delete feature/payment-integration
# List stale branches (not merged in 30 days)
git branch --no-merged main --sort=-committerdate
# Batch delete merged branches
git branch --merged main | grep -v '^\*\|main\|develop' | xargs -n 1 git branch -d
Observability Checklist
- Logs: Track branch creation/deletion in CI/CD pipeline logs
- Metrics: Monitor branch age (stale branches indicate workflow issues)
- Alerts: Alert on branches older than 30 days without activity
- Traces: Link branch names to issue tracker IDs for traceability
- Dashboards: Display open branches per developer in team dashboards
Security/Compliance Notes
- Branch names can appear in logs and URLs — avoid embedding secrets or sensitive project codenames
- Protected branches (main, release) should require PR approval before merging
- Use branch protection rules to prevent force pushes to shared branches
- Audit branch deletion with
git reflogfor compliance requirements - Consider signed commits on release branches for supply chain security
Common Pitfalls / Anti-Patterns
- Branch hoarding — keeping dozens of abandoned branches clutters the repository and confuses CI
- Naming without context —
fix-bugtells no one what was fixed or why - Switching with dirty working tree — uncommitted changes follow you to the new branch, causing confusion
- Deleting before verifying merge — always use
-d(safe delete) instead of-D(force delete) - Ignoring remote branches — local branches can diverge from their remote counterparts; use
git fetchregularly - Working directly on main — defeats the purpose of branching; always create a feature branch
Quick Recap Checklist
- List branches with
git branchandgit branch -a - Create branches with
git switch -c <name> - Switch branches with
git switch <name> - Delete merged branches with
git branch -d <name> - Delete remote branches with
git push origin --delete <name> - Use descriptive, hierarchical naming conventions
- Clean up stale branches regularly
- Recover deleted branches via
git reflog
Interview Q&A
git branch and git checkout (or git switch)?git branch creates or lists branches but does not change your working directory. git switch (or git checkout) changes your working directory to match the target branch. The modern best practice is git switch -c to create and switch in one step.
git branch -d refuses to delete an unmerged branch, protecting your work. git branch -D force deletes it regardless. The commits aren't immediately lost — they remain reachable via git reflog for typically 90 days until garbage collection removes them.
A detached HEAD occurs when you check out a specific commit instead of a branch. Any new commits won't belong to any branch. To recover, create a new branch at the current commit: git switch -c recovery-branch.
Git branches are simply pointers to commit SHAs, not copies of files. Creating a branch means writing a 41-byte file (the SHA reference) to .git/refs/heads/. This is why branching is instantaneous even in massive repositories, unlike SVN which copies the entire tree.
Use git reflog to find the last commit SHA of the deleted branch, then recreate it: git branch recovered-branch <sha>. The reflog records every HEAD movement and is retained for 90 days by default.
Branch Architecture: Lightweight Pointers
Branches in Git are not copies of files — they are lightweight pointers to commit SHAs. Understanding this mental model explains why branching is instantaneous and why operations like git switch only change which commit your working tree points to.
graph LR
HEAD["HEAD"] -->|points to| Feature["feature (branch ref)"]
Feature -->|points to| E["commit E"]
E --> D["commit D"]
D --> C["commit C"]
C --> B["commit B"]
B --> A["commit A"]
Main["main (branch ref)"] -.->|also points to| C
classDef pointer fill:#16213e,color:#00fff9
class HEAD,Feature,Main pointer
The branch ref file (.git/refs/heads/feature) contains only a 40-character SHA. Switching branches means updating HEAD to point to a different ref file — no file copying, no history duplication.
Production Failure: Deleting Unmerged Branches
Scenario: A developer runs git branch -D feature/payment to clean up, not realizing the branch contained two weeks of unremerged payment integration work. The branch was never pushed to remote.
Impact: Complete loss of unremerged work. The commits become dangling objects, reachable only via git reflog for 90 days before garbage collection permanently removes them.
Mitigation:
- Always use
git branch -d(safe delete) which refuses to delete unmerged branches - Push feature branches to remote before local cleanup
- Run
git branch --no-mergedbefore any bulk deletion - Set up a pre-delete hook or alias that warns about unmerged branches
# Safe cleanup workflow
git branch --no-merged main # see what would be lost
git branch --merged main # safe to delete
git branch --merged main | grep -v '^\*\|main\|develop' | xargs git branch -d
Trade-offs: git switch vs git checkout
| Dimension | git switch | git checkout |
|---|---|---|
| Safety | Explicit intent — only switches branches | Overloaded — switches branches AND restores files, easy to misuse |
| Clarity | Self-documenting: switch means change branch | Ambiguous: checkout file vs checkout branch do different things |
| Git version | Requires Git 2.23+ (Aug 2019) | Available in all Git versions |
| Create + switch | git switch -c branch | git checkout -b branch |
| Discard changes | git switch --discard-changes branch | git checkout -- branch |
| Detached HEAD | git switch --detach <commit> | git checkout <commit> |
| Recommendation | Use for all branch switching | Use only on older Git versions or for file restoration |
Implementation: Branch Naming Convention Enforcement
Enforce consistent branch naming via a prepare-commit-msg or pre-commit hook:
#!/bin/bash
# .git/hooks/pre-commit — enforce branch naming conventions
BRANCH_NAME=$(git symbolic-ref --short HEAD 2>/dev/null)
if [ -z "$BRANCH_NAME" ]; then
exit 0 # detached HEAD, skip
fi
# Pattern: type/description (e.g., feature/login, fix/bug-123, hotfix/security)
PATTERN="^(feature|fix|hotfix|release|refactor|docs|chore)/[a-z0-9-]+$"
if ! echo "$BRANCH_NAME" | grep -qE "$PATTERN"; then
echo "ERROR: Branch name '$BRANCH_NAME' does not follow naming convention."
echo "Expected: type/description (e.g., feature/user-auth, fix/login-bug)"
echo "Valid types: feature, fix, hotfix, release, refactor, docs, chore"
exit 1
fi
Make it executable: chmod +x .git/hooks/pre-commit
Summary Checklist
- Branches are lightweight pointers, not file copies
- Use
git switchfor branch operations (Git 2.23+) - Use
git branch -d(safe) not-D(force) for deletion - Push branches to remote before local cleanup
- Enforce naming conventions with hooks
- Run
git branch --no-mergedbefore bulk deletion - Recover deleted branches via
git reflogwithin 90 days
Resources
Category
Related Posts
Master git add: Selective Staging, Patch Mode, and Staging Strategies
Master git add including selective staging, interactive mode, patch mode, and staging strategies for clean atomic commits in version control.
Git Cherry-Pick: Selectively Applying Commits
Master git cherry-pick to selectively apply commits between branches. Learn use cases, pitfalls, and best practices for targeted commit transplantation.
Rebase vs Merge: When to Use Each in Git
Decision framework for choosing between git rebase and git merge. Understand trade-offs, team conventions, history implications, and production best practices.