Branches are pointers
A git branch is just a 41-byte file under .git/refs/heads/ — a movable pointer to one commit. HEAD points at the current branch. That is why creating a branch is O(1) and cheap, unlike systems that copy whole working directories.
People talk about git branches as if each one were a heavy copy of the whole project — so they hesitate to make them, the way you would hesitate to duplicate a 2 GB folder. The truth is almost comically small: a branch in git is a single text file containing one 40-character commit ID. Making a branch writes 41 bytes. That is the entire cost.
By the end of this lesson you will be able to say exactly what a branch is on disk, what HEAD points at, and why a commit “moves” a branch forward instead of copying anything.
After this lesson you can explain that a branch is a movable pointer (a ref) to a commit stored as a file under .git/refs/heads/, describe the HEAD → branch → commit chain, and say why creating a branch and committing on it are both cheap, constant-time operations.
A branch is a named, movable pointer to one commit — nothing more. Git stores it as a tiny file. List your branches with:
git branch
# * main
# feature-loginThe * marks the branch you are currently on. Each of those names corresponds to a file on disk. Look directly:
cat .git/refs/heads/main
# 9d1f7a3c2b8e4a6f0c1d2e3f4a5b6c7d8e9f0a1bThat 40-character hex string is the commit ID the branch points to. The whole branch is that one line. main is not special to git — it is just the conventional name of the default branch a fresh repo starts on (older repos used master).
Committing moves the current branch pointer forward; it never copies the branch. When you make a commit, git writes the new commit object, then rewrites the current branch file to hold the new commit’s ID. The old commit is still there — the new one records it as its parent. So a branch is always the tip of a chain that reaches back through history:
git commit -m "Add login form"
# main now points at the new commit;
# the new commit's parent is the previous tipNothing about the other branches changed. Only the file for the branch you were on got a new 40-character string written into it.
HEAD is a pointer to the current branch — a pointer to a pointer. Git needs to know which branch “you are on” so a commit knows which file to advance. That is HEAD. Normally it does not point at a commit directly; it points at a branch name:
cat .git/HEAD
# ref: refs/heads/mainRead that as a chain: HEAD → main → commit 9d1f7a3…. When you switch branches, only HEAD changes — it is repointed to the other branch’s file. When you commit, HEAD tells git which branch file to move. Two levels of indirection, both just text.
▸Why this works
This indirection is why git branching is cheap while older systems were not. In tools that branched by copying the working tree (or a server-side path), a branch cost time and disk proportional to the project size. In git, the commits already exist as shared, immutable objects; a branch only adds a name pointing into that existing graph. Creating a branch is O(1) — one small file — no matter whether the repo is 10 files or 100,000.
“Detached HEAD” is when HEAD points straight at a commit, skipping the branch level. If you check out a specific commit instead of a branch, HEAD holds a commit ID directly rather than a ref: refs/heads/… line:
git checkout 9d1f7a3
# You are in 'detached HEAD' state.
cat .git/HEAD
# 9d1f7a3c2b8e4a6f0c1d2e3f4a5b6c7d8e9f0a1bYou can look around and even commit, but those commits belong to no branch — switch away and they become unreachable (eventually garbage-collected). It is useful for inspecting old states, dangerous for new work. You will see how to enter and leave this state deliberately in the next lesson.
Watching the pointers move.
Start on main, pointing at commit A. So HEAD → main → A.
Create a branch and stay put:
git branch featureNow two files under .git/refs/heads/ — main and feature — both contain A’s ID. HEAD still points at main. No commits were copied; you just added one 41-byte file.
Switch to it and commit:
git switch feature
git commit -m "Start feature" # creates commit B, parent AHEAD → feature → B, and B’s parent is A. The main file is untouched — it still holds A. The two branches have now diverged: main at A, feature at B. Everything that happened was: one new commit object written, and the four bytes-ish of the feature file rewritten from A’s ID to B’s ID. That is the whole machinery of branching.
▸Common mistake
A common confusion: “if I delete a branch, do I lose those commits?” Deleting a branch only removes the pointer — the commit objects stay in the object store as long as something else can reach them (another branch, a tag, or your reflog). Deleting feature after merging it into main loses nothing, because main can still reach those commits. Deleting an unmerged branch is what git warns you about, because then the pointer was the only way back to that work.
What physically happens on disk when you create a new branch in git?
A branch is a movable pointer to a single commit, stored as a 41-byte file under .git/refs/heads/ — that small file is the branch. HEAD is a pointer to the current branch (ref: refs/heads/main), forming the chain HEAD → branch → commit. Committing advances the current branch’s file to the new commit; nothing is copied, so creating a branch and committing are both cheap, constant-time operations. When HEAD points straight at a commit instead of a branch, you are in detached HEAD — fine for looking, risky for new work. Next you will learn the commands that create these pointers and move HEAD between them: git switch and git branch.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.