---
title: "Branches & HEAD"
description: "Branches move with new commits; HEAD marks which reference moves next."
source: https://vizlearn.app/cs/version-control/branches-head
---

# Branches & HEAD

A Git branch is a movable name for one commit. `HEAD` usually names the branch you have checked out, so the next commit advances that branch and leaves every other branch alone. Detached HEAD removes the middle name: new commits move `HEAD` itself, not a branch ref.

This is why branches are cheap. Creating one does not copy files, commits, or a line of history. Git writes another name that contains the same commit ID. The histories only diverge after a new commit moves one name away from the shared tip.

## How branches and HEAD work

The lesson keeps three kinds of state separate:

- A **commit object** is immutable. It records a tree, an optional parent, identity data, and a message. Its object ID comes from those bytes.
- A **branch ref** such as `refs/heads/main` contains one commit ID. Git can replace that ID as the branch advances.
- **HEAD** is normally symbolic: it contains the name of the current branch ref. In detached mode, it contains a commit ID directly.

When attached, the lookup has two hops: `HEAD → refs/heads/main → 6f09690`. A commit first resolves that chain to find its parent, writes the new immutable object, and only then updates `main`. Keeping the object write and ref write as separate beats matters. The object database can contain a commit before any branch points to it.

`git branch feature` resolves the current HEAD and writes `refs/heads/feature` with that ID. It does not switch HEAD. If `main` already points to `6f09690`, both names now point to that one object.

`git switch feature` changes symbolic HEAD to name `refs/heads/feature`. The command does not move either branch tip. In a real repository it may also update the index and working tree to match the selected commit; this lesson uses one fixed empty tree so that ref behavior is the only moving part.

## Branches & HEAD step by step

The default run executes six commands in a local repository:

```text
git commit --allow-empty -m "base"
git branch feature
git switch feature
git commit --allow-empty -m "feature-1"
git switch main
git commit --allow-empty -m "main-1"
```

The repository begins with an unborn branch. HEAD already names `main`, but `refs/heads/main` does not yet contain a commit ID.

1. The first commit resolves HEAD to no parent, then writes root commit `6f09690...`. Only after the object exists does `main` move from unborn to `6f09690...`.
2. `git branch feature` writes a second ref containing `6f09690...`. There is still one commit object. Nothing in the graph was copied, and HEAD still names `main`.
3. `git switch feature` changes HEAD from the name `main` to the name `feature`. Both refs remain on `6f09690...`.
4. The `feature-1` commit resolves `HEAD → feature → 6f09690...`, writes child commit `4395860...`, then moves only `feature` to that child. `main` stays on the shared base.
5. `git switch main` moves HEAD to the `main` ref. It does not drag `main` forward to the feature commit; the saved tip remains `6f09690...`.
6. The `main-1` commit uses the same base as its parent, writes child `1dc9ff9...`, and advances only `main`. The final graph has one shared root and two children. `main` and `feature` have diverged because different commits advanced different refs.

The short IDs shown in the player are unique prefixes. The engine stores full 40-character SHA-1-format IDs and uses the same fixed identity, time policy, empty tree, and commit serialization as the preceding **Commits as Snapshots** lesson.

## Why detached HEAD matters

Detached HEAD changes one rule: HEAD no longer names a branch ref. It points straight to a stored commit.

Committing is still legal. Git resolves the direct HEAD ID as the parent, writes a child commit, and updates HEAD to that child. No branch moves because no branch was selected. This is useful for inspecting or experimenting at an exact commit, but it creates a practical risk.

The two detached scenarios differ by one ref write. **Detached → unreferenced** detaches at `6f09690...`, creates `temp`, then switches back to `main`. The temporary commit remains in the object database, but no branch ref reaches it. The lesson marks it **unreferenced**, not deleted. Real Git normally records recent HEAD positions in the reflog, so the commit can often be recovered for a while. Object pruning and reflog expiration are separate mechanisms.

**Rescue detached work** inserts a branch before switching away:

```text
git branch rescue
git switch rescue
```

The first command writes `refs/heads/rescue` at the detached commit. The second makes HEAD symbolic again. The commit graph does not change; reachability changes because a durable name now leads to the commit.

## Branches & HEAD edge cases

- **Unborn HEAD.** `HEAD → main` is valid even before `main` has a target. The first commit creates the root object and gives the ref its first ID. This scoped engine refuses `git branch feature` until a commit exists because there is no valid target to copy into the new ref.
- **Duplicate branch name.** `git branch feature` fails when `refs/heads/feature` already exists. The existing ref is not overwritten, and no second pill appears.
- **Unknown branch.** `git switch missing` fails without changing HEAD, refs, or commit objects.
- **Unknown commit.** Detaching requires a full ID that names a stored commit in this engine. A missing object leaves HEAD unchanged.
- **Several refs at one commit.** This is ordinary state, not a merge. Each ref is an independent name holding the same ID.
- **Branch creation while detached.** The new branch receives the commit ID currently stored in HEAD. HEAD remains detached until a switch selects a branch.
- **Short ID collisions.** The player starts at seven characters and lengthens a prefix when needed. The full object ID remains the identity.
- **Display limits.** The sandbox accepts up to four branches, ten commits, and twenty commands so the graph stays readable. These are visualizer limits, not Git limits.

## Common mistakes with branches and HEAD

**"A branch contains its commits."** A branch stores one commit ID. History is found by following parent links from that commit.

**"Creating a branch copies history."** It writes another ref. If two refs name the same tip, every commit object behind that tip is shared.

**"HEAD is the newest commit."** HEAD records what is selected. It may resolve to an old branch tip or point directly to an older commit in detached mode.

**"Switching to a branch moves the branch."** Switching changes HEAD. Moving the destination ref to the old HEAD would destroy the saved tip you meant to visit.

**"Every branch at the parent advances on commit."** Only the branch named by symbolic HEAD advances. If every matching ref moved, independent lines of work would collapse together.

**"An unreferenced commit is deleted immediately."** Losing branch reachability and deleting an object are different events. Reflogs and garbage collection determine how long recovery remains possible in a real repository.

## What this Git model leaves out

The animation models one local, clean repository. Every demo commit uses `--allow-empty` over a fixed empty tree so file and index changes cannot obscure the ref update. Author, committer, and time data are fixed for deterministic IDs. The model omits checkout conflicts, remote and upstream refs, tags, merges, reflog UI, ref packing, branch deletion and renaming, hooks, signatures, replacement refs, concurrent filesystem writes, and garbage collection.

Those omissions narrow the experiment without changing its central rules: commit objects are immutable, branch refs are movable commit names, symbolic HEAD names a branch, and detached HEAD names a commit directly. For the object model underneath these commits, revisit **Commits as Snapshots**. For Git's reference details, see [`gitglossary`](https://git-scm.com/docs/gitglossary), [`git branch`](https://git-scm.com/docs/git-branch), and [`git switch`](https://git-scm.com/docs/git-switch).
