TechEarl

Git Branching Explained for Beginners

What a Git branch actually is, how HEAD points at your current spot, and the commands to create, switch, list, rename, and delete branches with confidence.

Ishan Karunaratne⏱️ 8 min readUpdated
Share thisCopied
Git branching explained for beginners, covering branches, HEAD, git checkout, git switch, and how to create, list, rename, and delete branches.

A Git branch is just a movable pointer to a commit. It is not a copy of your files, not a folder, and not an expensive thing to make. When you run git switch -c feature, Git writes a tiny file containing one commit ID and points HEAD at it. That cheapness is the whole reason branching in Git feels nothing like branching in older version-control systems: you make them constantly, throw them away freely, and never think about disk space.

If you have never touched branches before, this is the concept that unlocks the rest of Git. Once you understand that a branch is a label on a commit, everything from merging to rebasing to pull requests stops feeling like magic. This article is part of my Git for beginners series; if you are still getting your bearings with commits and the working tree, start there.

What a branch really is

Every commit in Git has a unique ID (a 40-character SHA-1 hash) and a pointer to its parent commit. String those parent links together and you get the history. A branch is nothing more than a named file that holds the ID of the commit at the tip of a line of work.

You can prove this to yourself. The branch is a literal file under .git/refs/heads/:

bash
cat .git/refs/heads/main
Creating and switching to a new branch, shown in git log graph output
Creating a branch, committing on it, and viewing the history graph.
text
3f8a1c9e6b2d4a7f0c1e9b8d6a3f2c1e9b8d6a3f

That is the entire branch: one line, one commit ID. When you add a new commit on main, Git updates that file to point at the new commit. The branch "moves forward" automatically. There is no copying, no cloning of files, nothing heavy happening. This is why creating a branch is instant even on a repository with years of history.

What HEAD is

HEAD is how Git tracks which branch you are currently on. It is another small file, .git/HEAD, and it almost always contains a reference to a branch rather than a raw commit ID:

bash
cat .git/HEAD
text
ref: refs/heads/main

Read that as "I am on the main branch." When you commit, Git advances whatever branch HEAD is pointing at. When you switch branches, Git rewrites HEAD to point at the new branch and updates the files in your working directory to match that branch's tip commit.

So there are two layers of indirection: HEAD points at a branch, and the branch points at a commit. That sounds like overhead, but it is exactly what lets the same commit be the tip of multiple branches, and what makes switching branches an O(1) operation.

Detached HEAD

If you ever check out a specific commit instead of a branch, HEAD points straight at that commit with no branch in between. Git calls this a detached HEAD, and it warns you loudly:

text
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

It is not an error. It just means new commits you make are not attached to any branch, so they are easy to lose. To keep work you did in a detached HEAD, make a branch for it: git switch -c keep-this-work. If you have already lost commits this way, they are usually recoverable; see how to recover lost commits with git reflog.

Creating and switching branches

Modern Git (2.23 and later, released August 2019) gives you two purpose-built commands: git switch for moving between branches and git restore for discarding changes. Before that, both jobs lived inside the heavily overloaded git checkout, which is still around and still works. That same overload is why checkout could also restore or throw away files, a footgun I unpack in how git switch and git checkout discard changes. I will show both here because you will see checkout everywhere in older tutorials and Stack Overflow answers.

To create a new branch and move onto it in one step:

bash
# The modern way (Git 2.23+)
git switch -c feature-login

# The classic way (works in every version of Git)
git checkout -b feature-login

Both do the same thing: create a branch named feature-login pointing at your current commit, then move HEAD onto it. The -c (switch) and -b (checkout) flags mean "create."

If the branch already exists and you just want to move onto it, drop the create flag:

bash
# Modern
git switch feature-login

# Classic
git checkout feature-login

To create a branch without moving onto it (you stay where you are), use git branch:

bash
git branch feature-login

Here is how the two generations of commands line up:

TaskModern command (2.23+)Classic command
Create a branch and switch to itgit switch -c namegit checkout -b name
Switch to an existing branchgit switch namegit checkout name
Create a branch without switchinggit branch namegit branch name
Go back to the previous branchgit switch -git checkout -
Create a branch from a specific commitgit switch -c name <commit>git checkout -b name <commit>

My recommendation for anyone learning today: use git switch and git restore. They were split out precisely because checkout did too many unrelated things, which made it a common source of "I ran the wrong thing and lost my changes" mistakes. Knowing checkout is still worthwhile because the older form is everywhere, but reach for switch first.

Listing branches

To see every local branch, with the current one marked by an asterisk:

bash
git branch
text
* main
  feature-login
  bugfix-header

The branch with the * is the one HEAD is pointing at. For more detail (the tip commit and its subject line for each branch), add -v:

bash
git branch -v

To include remote-tracking branches (the ones living on origin), use -a for all:

bash
git branch -a
text
* main
  feature-login
  remotes/origin/main
  remotes/origin/feature-login

The remotes/origin/... entries are Git's local record of where the branches were on the remote the last time you fetched. They are not the same as your local branches; understanding that gap is the heart of the difference between git pull and git fetch.

Renaming a branch

To rename the branch you are currently on, use -m (move) with the new name:

bash
git branch -m new-name

To rename a branch you are not on, give both names:

bash
git branch -m old-name new-name

Renaming only touches the local branch. If you have already pushed the old name to a remote, you need to push the new one and delete the old one there. This is also how people rename master to main on existing projects:

bash
git branch -m master main
git push origin main
git push origin --delete master

After that, anyone else on the repo needs to update their local copy to track the renamed branch, and you should update the default branch in your hosting provider's settings. While you are in there, it is a good moment to set up a branch protection rule on main so nobody force-pushes over the renamed branch by accident.

Deleting a branch

Once a branch is merged and you are done with it, delete it. Use -d (safe delete), which refuses to remove a branch whose commits have not been merged into your current branch:

bash
git branch -d feature-login

If the branch has unmerged work, Git stops you:

text
error: The branch 'feature-login' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature-login'.

That is a guardrail, not a bug. If you genuinely want to throw the work away, -D (capital) forces it:

bash
git branch -D feature-login

Be deliberate with -D. It deletes the branch label, and any commits that were only reachable through that branch become "dangling." They are not instantly gone (the reflog remembers them for a while), but this is the command that strands work. To delete a branch on the remote as well:

bash
git push origin --delete feature-login

You cannot delete the branch you are currently on. Switch to another branch first, then delete.

A typical branching workflow

Here is the loop most beginners settle into, start to finish. It assumes you already have a project; if you do not, create a branch and switch onto it in one step only after you have set up a fresh repository to branch from:

bash
# Start from an up-to-date main
git switch main
git pull

# Branch off for the new work
git switch -c feature-search

# ...edit files, then stage and commit...
git add .
git commit -m "Add search box to header"

# Push the branch and set its upstream the first time
git push -u origin feature-search

The git add . step here glosses over what staging actually does; if you want a deeper look at the staging area before you commit on a branch, I cover it separately. That -u (short for --set-upstream) links your local branch to the remote one so future git push and git pull calls need no arguments. Skip it and Git complains that the current branch has no upstream branch. For the commit step itself, a clean message helps everyone who reads the history later; I cover that in Git commit message best practices.

Once the work is done and reviewed, you merge it into main (often through a pull request) and delete the feature branch. From there, the next thing to learn is what actually happens when branches come back together: git merge versus git rebase. Once a single feature branch feels comfortable, the next step up is team branching strategies like trunk-based and Git Flow, which decide how everyone's branches fit together.

Sources

Authoritative references this article was fact-checked against.

TagsGit BranchingGitVersion Controlgit switchgit checkout

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

The Git Staging Area Explained

The Git staging area (the index) is the in-between layer where you assemble exactly what goes into your next commit. Here is what git add really does, and why it exists.

Bash For Loops: Syntax, Examples, and One-Liners

Every form of the Bash for loop with working examples: brace-range, sequence-expression, array, glob, C-style, nested, and parallel. Plus the safe file-iteration patterns, common pitfalls, and macOS Bash 3.2 vs Linux Bash 4+ gotchas.