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.

published: reading time: 18 min read author: Geek Workbench updated: March 31, 2026

Introduction

Branching is Git’s superpower compared to 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.

Branch fundamentals unlock 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

ScenarioImpactMitigation
Accidentally delete unmerged branchLost workUse git branch -d (not -D); enable git reflog recovery
Switch with uncommitted changesWork carried to wrong branchUse git stash before switching, or git switch --discard-changes intentionally
Branch name collision with remotePush/pull confusionUse unique naming conventions; verify with git branch -a
Orphaned branches accumulateRepository clutterRegular git branch --merged cleanup; automate with CI
HEAD detached stateConfusion about where commits goUse 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-off Analysis

ApproachProsCons
git branch + git switchExplicit, clear intentTwo commands instead of one
git checkout -bSingle commandOverloads checkout with branch creation
Short-lived branchesEasy to merge, minimal driftRequires discipline to clean up
Long-lived branchesStable integration targetMerge conflicts accumulate, diverges from main
Descriptive namesSelf-documenting, searchableLonger to type
Short namesQuick to typeAmbiguous, 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 Considerations

  • 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 reflog for 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 contextfix-bug tells 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 fetch regularly
  • Working directly on main — defeats the purpose of branching; always create a feature branch

Quick Recap Checklist

  • List branches with git branch and git 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

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 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-merged before 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

Dimensiongit switchgit checkout
SafetyExplicit intent — only switches branchesOverloaded — switches branches AND restores files, easy to misuse
ClaritySelf-documenting: switch means change branchAmbiguous: checkout file vs checkout branch do different things
Git versionRequires Git 2.23+ (Aug 2019)Available in all Git versions
Create + switchgit switch -c branchgit checkout -b branch
Discard changesgit switch --discard-changes branchgit checkout -- branch
Detached HEADgit switch --detach <commit>git checkout <commit>
RecommendationUse for all branch switchingUse 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 switch for 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-merged before bulk deletion
  • Recover deleted branches via git reflog within 90 days

Interview Questions

1. What is the difference between 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.

2. What happens when you delete a branch that hasn't been merged?

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.

3. What is a detached HEAD state and how do you recover from it?

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.

4. Why are Git branches considered "lightweight" compared to other VCS?

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.

5. How do you find and recover a branch you accidentally deleted?

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.

6. How does Git internally store branch references?

Git stores each branch as a single file in .git/refs/heads/. The file contains only the 40-character SHA-1 hash of the commit it points to. This is why creating branches is instantaneous — Git simply writes one small file. The branch name maps to this file path, which is why branch names are case-sensitive on Linux but not on macOS/Windows due to filesystem differences.

7. What is the difference between a remote tracking branch and a local branch?

A local branch lives in your repository and you control it directly. A remote tracking branch (e.g., origin/main) is a local copy of the branch state on the remote — it updates when you run git fetch. Remote tracking branches are read-only; you cannot check them out directly. They exist purely to compare your local state against the remote state.

8. How does git switch --discard-changes differ from git stash?

git switch --discard-changes <branch> permanently discards uncommitted changes — they are gone. git stash temporarily saves changes to a stack, allowing you to recover them later with git stash pop. Use discard only when you intentionally want to abandon changes. Use stash when you need to switch context but might need those changes again.

9. What is the purpose of the reflog and how long is data retained?

The reflog (git reflog) records every position where HEAD has moved in your repository. It serves as a safety net for recovering lost commits. By default, reflog entries are retained for 90 days for reachable commits, and 30 days for unreachable commits (before garbage collection). Reflogs are local — they don't sync between machines or survive a fresh clone.

10. What happens during git fetch compared to git pull?

git fetch downloads remote commits, tags, and branches into your remote tracking branches — no local branches change. git pull does fetch + merge in one step — it updates your current local branch to incorporate remote changes. Fetch is safer for reviewing changes before integration; pull is faster for simple updates when you know the remote is ahead.

11. How do branch protection rules work in collaborative workflows?

Branch protection rules (configured in GitHub, GitLab, Bitbucket) enforce that certain branches require pull request reviews, status checks, or approval before merging. Common settings: require 2 approvals, block force-push, require signed commits, require status checks to pass. This prevents direct commits to protected branches and ensures code review before integration.

12. What is the difference between git merge and git rebase when integrating branch changes?

git merge creates a merge commit that ties together the histories of both branches — preserves context, non-linear history, safe for shared branches. git rebase rewrites your branch to apply commits on top of the target branch, creating a linear history — cleaner but dangerous on shared/public branches because it rewrites commit SHAs. Rebase is best for local feature branch cleanup before PR merge.

13. How do you recover from a detached HEAD state?

When you check out a specific commit (not a branch), you enter detached HEAD — commits aren't attached to any branch. To recover: git switch -c recovery-branch creates a new branch at the current commit. Alternatively, git branch backup-branch preserves the commit reference without switching. If you've made commits while detached, the recovery branch command ensures those commits aren't lost.

14. What workflow works best for teams using feature branches?

Effective team workflow: 1) Create feature branch from updated main, 2) Work and commit locally, 3) Push to remote when ready for CI, 4) Open PR for review, 5) After approval, squash-merge to main, 6) Delete feature branch after merge. Key practices: rebase onto main before PR (not after), use protected main branch, require passing CI before merge, delete branches after merge to reduce clutter.

15. Why should feature branches be short-lived?

Short-lived branches (hours to a few days) minimize merge conflict accumulation and integration debt. Long-lived branches (weeks/months) diverge significantly from main, making eventual merge painful with numerous conflicts. Branch age also signals workflow problems — old branches often mean work was abandoned or integration was repeatedly deferred. Target: merge within 1-3 days for typical features.

16. How do you create a branch from a remote tracking branch?

Use git switch -t origin/feature-x to create a local branch that tracks the remote. This is equivalent to git switch -c feature-x --track origin/feature-x. The local branch will track the remote and you can push to it with git push (with push.autoSetupRemote = true configured).

17. What is the difference between git branch -D and git branch -d?

-d (lowercase) is the safe delete — Git refuses to delete a branch if it contains unmerged commits, protecting your work from accidental loss. -D (uppercase) is the force delete — it deletes the branch regardless of merge status. Use -d first; only use -D when you're certain the branch is fully merged or the work is obsolete.

18. How do you rename a local branch?

Use git branch -m old-name new-name to rename the current branch, or git branch -m old-name new-name specifying both. Only local branch names are renamed — the remote branch name must be deleted and recreated by pushing the renamed branch.

19. What does git branch -vv show that regular git branch does not?

git branch -vv shows verbose output including upstream tracking relationship and ahead/behind count relative to the remote. For example: feature-x abc1234 [origin/feature-x: ahead 2, behind 1] means the local branch is 2 commits ahead and 1 behind the remote. This is the most detailed view for understanding the sync state between local and remote branches.

20. How do you set up a branch to always push to a specific remote?

Use git branch --set-upstream-to=origin/feature-x or git push -u origin feature-x when first pushing. With push.autoSetupRemote = true configured, Git automatically sets up the tracking relationship for new branches. Once tracked, git push without arguments pushes to the correct remote branch.

Further Reading

Conclusion

Branches are Git’s superpower for parallel development. Understanding the lightweight nature of branches — they’re just pointers — makes everything else: merging, rebasing, strategies. Master branch basics first, and the rest of Git becomes far more intuitive.

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 #staging #git-add

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.

#git #version-control #cherry-pick

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.

#git #version-control #rebase