Git branches intimidate people far more than they should, largely because the word "branch" implies something big and physical — a copy of the whole project you might damage. The truth is almost anticlimactic: a branch in Git is just a lightweight, movable pointer to a single commit. Internalise that one sentence and half of Git's apparent mystery evaporates.
Everything Git does with branches — creating, switching, merging, the dreaded detached HEAD — makes immediate sense once you picture commits as a chain and branches as sticky labels you move along it.
Commits form a chain; branches label it
Each commit is a snapshot that points back to its parent, forming a chain (really a graph) of history. A branch is nothing more than a named pointer to one commit in that graph — usually the latest on that line of work. Create a new branch and Git simply writes a new label pointing at your current commit; it copies nothing. That is why branching in Git is instant even on huge projects.
When you make a new commit on a branch, Git moves that branch's label forward to the new commit. The branch "grows" not by copying but by the pointer advancing along the chain of history.
HEAD and "detached HEAD"
HEAD is Git's pointer to "where you are right now" — normally it points at a branch, which points at a commit. When you switch branches, you are really just moving HEAD to point at a different label. The alarming "detached HEAD" state simply means HEAD is pointing directly at a commit instead of at a branch label — you are looking at history without a branch to catch new commits.
It sounds like damage and is not; it is just a warning that commits made here have no branch label, so they could be lost when you move away. Create a branch to keep them, and detached HEAD becomes a non-event.
Merging and rebasing, demystified
Merging combines two lines of history by creating a new commit with two parents, tying the branches together while preserving what happened. Rebasing instead replays your branch's commits on top of another branch, producing a cleaner straight-line history at the cost of rewriting those commits. Both are just operations on the graph of commits and the pointers into it — not magic, and not dangerous once you can picture the shapes.
The whole fear of Git tends to come from treating it as an opaque set of incantations. Replace that with the mental model — commits are a chain, branches and HEAD are movable labels — and commands you once copied nervously become obvious moves on a structure you can see in your head.
HEAD: the pointer that points at the pointer
Once a branch is understood as a movable label on a commit, HEAD becomes easy to explain precisely: it is a reference to whichever branch is currently checked out, meaning HEAD does not point directly at a commit under normal circumstances, it points at a branch, which itself points at a commit — an extra layer of indirection that is exactly what makes committing on a branch automatically move that branch's label forward along with HEAD, without needing to update HEAD itself as a separate step, since HEAD is just following whichever branch it currently references.
Detached HEAD, demystified by the same mental model
Checking out a specific commit hash directly, rather than a branch name, puts Git into 'detached HEAD' state, and the pointer model explains exactly why this state has its specific, sometimes-alarming behavior: HEAD is now pointing directly at a commit rather than at a branch that points at a commit, so any new commit made in this state has nothing pointing back to it once HEAD moves elsewhere — no branch label was ever created to keep referencing it — which is why a commit made in detached HEAD state can become effectively unreachable and eventually garbage-collected if nothing is done to preserve it, and why Git's warning message urges creating a new branch right there if the work is meant to be kept.
Merge versus rebase, understood as two different ways to update pointers
A merge creates a brand new commit with two parents, one from each branch being combined, preserving the actual history of both branches exactly as it happened, including every individual commit from each side. A rebase instead takes the commits unique to one branch and replays them, one at a time, on top of a different starting point, producing entirely new commits with new hashes that carry the same changes but sit at a different position in history — which is precisely why rebasing commits that have already been pushed and shared with others is considered dangerous: anyone else who already has the original commits now has a history that has diverged from the rebased version in a way that is painful, though not impossible, to reconcile afterward.
Why understanding the pointer model prevents the scariest-looking mistakes
The operations that feel most dangerous to a newcomer — deleting a branch, resetting to an earlier commit, checking out a detached HEAD — are, once the pointer model is genuinely internalized, understood to only ever move or remove labels, never the actual commit objects those labels point to, at least not immediately; a commit remains present and recoverable, via the reflog, for a real window of time even after the branch pointing at it is deleted, until Git's garbage collection eventually cleans up anything that has become fully unreachable. This is precisely why 'I deleted the wrong branch' is very often recoverable rather than catastrophic, and understanding that recoverability, rather than being told to simply be careful, is what actually removes the fear this article's own title names as its subject.
Why a branch pointer, not the commits themselves, is what 'checking out' moves
Switching branches with `git checkout` or `git switch` does not copy or move any commits at all — it moves HEAD to point at a different branch, and then updates the working directory's files to match whatever that branch's own tip commit contains, which is why switching between branches with very different histories can change many files in the working directory almost instantly: nothing computationally expensive is happening, only a pointer reassignment followed by writing out the files that particular commit already has recorded, exactly the same operation regardless of how far apart the two branches' histories actually are.
The reflog: a safety net built directly out of the pointer model
Git's reflog records every place HEAD has pointed, going back some configurable window, which is precisely what makes many supposedly catastrophic mistakes recoverable: a `git reset --hard` that seems to have discarded commits has, in the pointer model just described, only moved the branch pointer to a different commit — the original commit those changes lived on still exists in the object database and is still directly reachable via the reflog entry recorded right before the reset happened, which is exactly why `git reflog` combined with `git reset` back to the reflog-listed commit is the standard recovery move for an accidental hard reset, once the underlying pointer mechanics make clear why it works at all.
Why 'main' is not special to Git itself, only to convention
Nothing in Git's own internal mechanics treats a branch named `main` any differently from a branch named anything else — it is exactly the same kind of pointer as every other branch, and the convention of treating it as the primary, default branch is entirely a social and tooling convention (hosting platforms defaulting new repositories to it, teams agreeing to treat it as the source of truth) rather than anything enforced by Git itself, which is precisely why renaming it, or working in a repository that uses a different default branch name entirely, changes nothing about how branches actually behave underneath.
Why 'checking out a commit' and 'checking out a branch' feel similar but are not
Both operations update the working directory to match a specific commit's files, which is why they can feel interchangeable in casual use, but only checking out a branch also moves HEAD to point at that branch's own pointer, ready to advance automatically with the next commit; checking out a bare commit hash puts HEAD directly on that commit instead, with no branch pointer following along — precisely the detached-HEAD state discussed earlier — which is the concrete difference that explains why committing after each of the two superficially similar actions produces such different, and sometimes surprising, downstream results.
Orphan branches: a pointer with a genuinely empty history behind it
`git checkout --orphan` creates a new branch with no commit history at all — not even a shared ancestor with any existing branch — which is a useful, if unusual, way to see the pointer model at its most stripped-down: a branch is nothing more than a name that will point at whatever commit is made next, and an orphan branch simply starts that pointer with nothing behind it yet, commonly used for maintaining an entirely separate history, like generated documentation pages, within the same repository without any of its commit history being tangled up with the main project's own history at all.
Why deleting a branch does not delete its commits, restated concretely
`git branch -d feature-x` removes exactly one thing: the pointer named `feature-x`; every commit that pointer referenced remains fully present in the repository's object database, reachable through the reflog and through any other branch or tag that also happens to reference it, and it is only once no reference at all points to a given commit, and enough time has passed for garbage collection to run, that the commit becomes genuinely unrecoverable — which is the concrete mechanical reason 'I deleted the wrong branch' is, in the overwhelming majority of real cases, a recoverable mistake rather than the permanent one it feels like in the moment.
Tags: a pointer that, unlike a branch, is meant to never move
A tag is the pointer model's other common variant, and the distinction from a branch is entirely about intended mutability rather than mechanism: a branch pointer is expected to move forward with every new commit, while a tag is created once, at a specific commit, and conventionally never moved afterward, typically used to mark a specific release — understanding both as the same underlying kind of named reference to a commit, differing only in the convention around whether they are expected to move, ties together two concepts that are often taught as unrelated but are mechanically close cousins of each other.
Why 'git branch -f' is more dangerous than an ordinary checkout
Force-moving a branch pointer directly to a different commit with `git branch -f` skips the safety checks an ordinary merge or rebase would normally perform, silently discarding, from that branch's perspective, any commits it previously pointed to that are not reachable from the new target — those commits are not deleted outright, per the reflog safety net discussed earlier, but the branch itself no longer reflects them at all, which is exactly why this specific command deserves more caution than routine pointer-moving operations like an ordinary checkout or fast-forward merge.