skip to content

questions

3

In Git, what is HEAD, and how does it differ from a branch name?

level: juniorimportance: must knowfreq 70%

answer

  1. it usually holds text, not a hash
  2. one extra hop before the commit
  3. it answers "which branch", not "which commit"
  4. that hop is why commits advance a branch

basics

~20 s

HEAD is a pointer to whatever you currently have checked out. Normally it is a symbolic ref holding the name of a branch, such as refs/heads/main, while the branch itself is a ref holding one commit hash.

solid answer

~40 s

A **ref** is just a name holding a commit hash, living in a namespace: `refs/heads/` for local branches, `refs/tags/` for tags, `refs/remotes/` for remote-tracking refs. **HEAD** is one level of indirection above that — normally a *symbolic ref* whose content is the text `ref: refs/heads/main`, meaning "I am on main". That indirection is what makes committing work: Git resolves HEAD to a branch, resolves the branch to a commit, uses that commit as the new commit's parent, and then writes the new hash back into the branch — so the branch advances and HEAD keeps naming the branch. `git symbolic-ref HEAD` shows the branch HEAD names; `git rev-parse HEAD` resolves all the way down to the commit hash.

code

console · 6 lines
console
$ cat .git/HEAD
ref: refs/heads/main
$ git symbolic-ref HEAD
refs/heads/main
$ git rev-parse HEAD
d2b5d5322e03b5733ebfa5debcd995cfc63f718f

go deeper

for a junior

Recall that HEAD means "where I am now" and normally names the current branch, while a branch is a name holding one commit hash. Know that git rev-parse HEAD gives the commit.

for a middle

Explain the two-step resolution and why it matters: commits update the ref HEAD names, which is how a branch advances without HEAD ever changing its own text.

for a senior

Use the model diagnostically — separate "which ref moved" from "which commit exists", and know that each worktree carries its own HEAD plus its own transient refs such as ORIG_HEAD and MERGE_HEAD.

for a principal

Own the mental model you expect on the team: refs are cheap mutable names over an immutable object graph, so process design should treat losing a ref as recoverable and treat rewriting objects as the genuinely expensive event.

## Refs are names holding hashes Almost everything in Git that looks like a name is a **ref**: a full name such as `refs/heads/main` whose value is an object id. Refs live in namespaces, and the namespace is the whole meaning of the name: - `refs/heads/<name>` — local branches. Git advances these for you when you commit. - `refs/tags/<name>` — tags. By convention nothing ever moves them. - `refs/remotes/<remote>/<name>` — remote-tracking refs, updated by fetch to record where the remote's branches were. Because a ref is nothing more than a name plus a hash, creating a branch is a constant-time operation regardless of repository size, and deleting one removes a name, not commits. ## HEAD is a symbolic ref HEAD lives beside those namespaces rather than inside them, and it usually does not hold a hash at all. It holds text of the form `ref: refs/heads/main` — a **symbolic ref**, a pointer to another ref. Reading it is a two-step resolution: HEAD to branch, branch to commit. That extra step is exactly what distinguishes "which branch am I on" from "which commit am I at". Both questions have answers, and Git gives you a different command for each: `git symbolic-ref HEAD` prints `refs/heads/main` (or fails if there is no branch to name), while `git rev-parse HEAD` prints the commit hash. ## What the indirection buys When you commit, Git builds the commit object with the commit HEAD currently resolves to as its parent, then updates *the ref HEAD names* to the new commit. Because HEAD points at the branch rather than at a commit, the branch moves forward and HEAD needs no update. Switching branches is the reverse: Git rewrites HEAD's text to name a different branch and then makes the working tree and index match that branch's commit. ## Other HEAD-like names A handful of special refs sit alongside HEAD at the top of the repository rather than under `refs/`, and they exist to record transient state: `ORIG_HEAD` (where HEAD was before a drastic move), `FETCH_HEAD` (what the last fetch retrieved), `MERGE_HEAD` (the other side during a merge in progress), and the corresponding markers for cherry-pick and revert. They are read like any other revision name. ## Common confusions to clear up HEAD is not a branch, and it is not simply "the latest commit" — it is the pointer that tells Git which branch is current. It is also not shared the way people assume: each worktree has its own HEAD, which is what lets two checkouts of the same repository sit on different branches. And HEAD is spelled in capitals; on a case-insensitive filesystem `git head` still is not a command.

  • What is the difference between `git symbolic-ref HEAD` and `git rev-parse HEAD`?
    `git symbolic-ref HEAD` reads one level and prints the ref HEAD names, such as `refs/heads/main`; it fails outright when HEAD holds a raw hash instead. `git rev-parse HEAD` resolves the chain to the end and prints the commit id, which always works.
  • If both a branch and a tag are named `v1.0`, what happens when you write `v1.0` on the command line?
    Git warns that the refname is ambiguous, and resolution follows a fixed order that checks `refs/tags/` before `refs/heads/`, so the tag usually wins; some commands treat the ambiguity as an outright error. The fix is to spell the full ref path, such as `refs/heads/v1.0`.

saying these in an interview costs you the question

  • Says HEAD is the latest commit in the repository
  • Thinks HEAD is a branch you can commit onto directly
  • Believes deleting a branch deletes its commits
  • Claims every repository has exactly one HEAD, even with multiple worktrees

context

open as a page

What does detached HEAD mean in Git, and how do you keep commits made in that state?

level: middleimportance: must knowfreq 66%

basics

~10 s

Detached HEAD means HEAD holds a commit hash directly instead of naming a branch. Commits still work, but nothing points at them, so create a branch at that commit before switching away.

open as a page

In Git, why might a branch exist with no file for it under .git/refs/heads?

level: seniorimportance: nice to knowfreq 22%

basics

~10 s

Because refs can be stored two ways. Git compacts loose one-file-per-ref storage into a single .git/packed-refs file, so a branch may exist only as a line there. Loose files, when present, take precedence.

open as a page