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/:
cat .git/refs/heads/main
3f8a1c9e6b2d4a7f0c1e9b8d6a3f2c1e9b8d6a3fThat 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:
cat .git/HEADref: refs/heads/mainRead 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:
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:
# The modern way (Git 2.23+)
git switch -c feature-login
# The classic way (works in every version of Git)
git checkout -b feature-loginBoth 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:
# Modern
git switch feature-login
# Classic
git checkout feature-loginTo create a branch without moving onto it (you stay where you are), use git branch:
git branch feature-loginHere is how the two generations of commands line up:
| Task | Modern command (2.23+) | Classic command |
|---|---|---|
| Create a branch and switch to it | git switch -c name | git checkout -b name |
| Switch to an existing branch | git switch name | git checkout name |
| Create a branch without switching | git branch name | git branch name |
| Go back to the previous branch | git switch - | git checkout - |
| Create a branch from a specific commit | git 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:
git branch* main
feature-login
bugfix-headerThe 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:
git branch -vTo include remote-tracking branches (the ones living on origin), use -a for all:
git branch -a* main
feature-login
remotes/origin/main
remotes/origin/feature-loginThe 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:
git branch -m new-nameTo rename a branch you are not on, give both names:
git branch -m old-name new-nameRenaming 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:
git branch -m master main
git push origin main
git push origin --delete masterAfter 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:
git branch -d feature-loginIf the branch has unmerged work, Git stops you:
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:
git branch -D feature-loginBe 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:
git push origin --delete feature-loginYou 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:
# 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-searchThe 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.
- Pro Git: Branches in a Nutshellgit-scm.com
- git-switch: Official Git documentationgit-scm.com
- git-branch: Official Git documentationgit-scm.com




