Module 5 — Branching (git branch, switch, checkout)

Updated 8 September 2026

Module 5 — Branching (git branch, switch, checkout). Branches are why Git won. They make parallel lines of work — a feature, a hotfix, an experiment — cost nothing to start and nothing to abandon. And because of Module 3, you already know the secret: a branch is a 41-byte file. This module makes that fact into daily practice, and defuses Git's most famous scare, the detached HEAD.

🧠 concept → 🧩 analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)

Before you start. You need Module 3 (refs, HEAD, the object graph) and Module 4 (log --oneline, --graph, revision names). The exercises continue in the kitchen repository, including Module 3's Exercise 3.7 commit. Tools: just Git and a terminal.
This page contains Mermaid diagram blocks. Notion shows them as code by default — click the block and switch it to Preview to see the diagram. You only need to do this once per block.

Part A — What a branch actually is

A1. A branch is a movable name for one commit

Module 3, Exercise 3.6: main is a text file holding one commit hash, and committing overwrites it with the new hash. That is the complete definition — a branch is a named, movable pointer to a commit. Creating a branch (git branch <name>) writes one more 41-byte file pointing at the same commit you are on. No files are copied, no "branch folder" exists, nothing about your project changes. Two names now reference one commit; history is still one chain. The pointers only begin to differ when commits start moving them (Part B).

git branch with no arguments lists your branches, with * marking the one HEAD names; git branch -v adds each pointer's current commit — worth preferring, because it displays exactly the mental model: names → hashes.

🧪 Exercise 5.1 — create a branch and prove how cheap it was
bash
cd ~/git-course/kitchen
git branch sourdough          # create; do NOT switch to it yet
git branch -v                 # both names, and where each points
cat .git/refs/heads/sourdough
cat .git/refs/heads/main      # compare the two files
Expected result — click to reveal
plain text
* main      cff4379 Add copy of serving info
  sourdough cff4379 Add copy of serving info
cff4379b8d3816585024559acacc1a8c68ed5cd7
cff4379b8d3816585024559acacc1a8c68ed5cd7

What to read out of it: two branches, one commit — -v shows both names resolving to the same hash, and the two cats prove it at the byte level: identical file contents (your hash differs from this page's; the two files matching each other is the point). The * marks HEAD's branch — creating sourdough did not move you onto it. Total cost of the new branch: one 41-byte file. This is why Git users branch for everything — the operation is O(1) regardless of repository size, and always will be.

Real-world analogy — two bookmarks in one book

A branch is a bookmark. Putting a second bookmark at the page you are reading costs nothing and copies no pages — the book does not know or care. Each reader's progress is just "which page their bookmark is on," and moving a bookmark never changes the text. Ten bookmarks, one book.

Where the analogy stops working. Two readers of one book share the same fixed pages forever. Git's "readers" write: from the shared page onward, each branch can add its own new pages, and the book becomes a choose-your-own-adventure — one spine of shared history that forks into different continuations (B2 shows the fork happen). No physical book grows different endings depending on which bookmark you carried; Git's does, and merging those endings back together is Module 6's whole subject.

A2. Why this design wins

In older VCSs (Module 1's SVN), a branch was a server-side directory copy — heavyweight enough that teams branched rarely and dreaded it. Git's pointer design inverts the economics: creating, switching, and deleting branches are constant-time pointer operations, so the workflow changes shape. Risky refactor? Branch. Quick experiment? Branch. Production hotfix while mid-feature? Branch. Abandon an idea by deleting its pointer — the 41 bytes are gone and (eventually) its unreferenced commits are garbage-collected, with no ceremony. Every collaboration model in Module 9 is built on the assumption that branches are free; this section is where that assumption becomes intuition.

Diagram source
flowchart RL
    M["main"] --> C3["commit cff4379"]
    S["sourdough"] --> C3
    H["HEAD"] -.->|"ref:"| M
    C3 --> C2["commit 04776eb"]
    C2 --> C1["...older commits"]

🎯 Interview questions — Part A

🎯 "What is branching in Git?" — asked verbatim in GeeksforGeeks' 70+ Git Interview Questions, updated Jul 2026

Branching creates independent lines of development inside one repository. Mechanically, a branch is a movable pointer — a ~41-byte ref file holding one commit hash; creating one copies nothing. Commits made while "on" a branch advance that branch's pointer only, so different branches accumulate different histories from a shared ancestor. Work is isolated: a half-finished feature on its own branch cannot break main, and switching branches swaps your working tree between the lines of work. Branches are later reunited by merge or rebase (Modules 6 and 10).

The details that separate candidates: an average answer says "a separate line of development, like a copy of the code." The word "copy" is the tell — a strong answer corrects it: nothing is copied; commits are shared up to the fork point, and the branch is only a pointer (which is why creation is instant on any repo size). Deeper still: "independent lines" is a property of the commit graph (diverging parent chains), not of the branch refs — delete a branch ref and the commits still exist until garbage collection. Tying branch cheapness to workflow consequences (feature branches, PR models — Module 9) shows you understand why the design matters, not just what it is.

Part B — Switching, and what moves

B1. git switch — moving HEAD

Official docs: git-switch manual

git switch <branch> does three things, in Module 3's vocabulary: rewrites .git/HEAD to ref: refs/heads/<branch>; resets the index to that branch's tip commit; and updates your working tree to that commit's snapshot. From then on, commits advance the new branch. That is all "being on a branch" means — which HEAD points at, and therefore which pointer your commits push forward.

🧪 Exercise 5.2 — switch, commit, and watch only one pointer move
bash
cd ~/git-course/kitchen
git switch sourdough
echo "starter: feed daily" > starter.txt
git add starter.txt && git commit -m "Add sourdough starter notes"
git branch -v                 # who moved?
Expected result — click to reveal
plain text
Switched to branch 'sourdough'
[sourdough 6509950] Add sourdough starter notes
 1 file changed, 1 insertion(+)
 create mode 100644 starter.txt
  main      cff4379 Add copy of serving info
* sourdough 6509950 Add sourdough starter notes

What to read out of it: the commit line's prefix changed — [sourdough 6509950], not [main …]: the commit advanced the branch HEAD names, exactly as Module 3 C2 promised. branch -v shows the split beginning: sourdough moved one commit ahead; main stayed put. Nothing happened to main's history — it is untouched by anything you do over here. That sentence is the entire safety model of branch-based work.

🧪 Exercise 5.3 — switch back, and "lose" a file safely
bash
cd ~/git-course/kitchen
git switch main
ls                            # where did starter.txt go?
git log --oneline -1          # and where did the commit go?
Expected result — click to reveal
plain text
Switched to branch 'main'
baking.txt
bread.txt
method.txt
serving-copy.txt
serving.txt
cff4379 Add copy of serving info

What to read out of it: starter.txt is not in the directory, and the log's newest entry is main's tip — no starter commit. Nothing is lost: the working tree always displays the current branch's snapshot, and main's snapshot never contained the file. The commit and its blob sit in the object database (git log --oneline sourdough or git show sourdough:starter.txt proves it from right here — Module 4 skills on a branch name). First-time users panic at this moment; you know the working tree is a view, not the data. Switch back and forth once more and feel how the directory reshapes itself.

B2. Divergence — the fork made visible

So far sourdough is simply ahead of main. True branching begins when both lines move: commit on main too, and the histories diverge — two children of one ancestor, growing independently. Module 4's --graph finally earns its keep, with one addition: --all shows commits reachable from every branch, not just HEAD's (log's default start point is HEAD, so without --all the other branch's commits are invisible — not gone).

🧪 Exercise 5.4 — make the lanes split
bash
cd ~/git-course/kitchen        # on main, from 5.3
echo "rye flour variant" > rye.txt
git add rye.txt && git commit -m "Add rye variant notes"
git log --oneline --graph --all | head -6
Expected result — click to reveal
plain text
[main de08e42] Add rye variant notes
 1 file changed, 1 insertion(+)
 create mode 100644 rye.txt
* de08e42 Add rye variant notes
| * 6509950 Add sourdough starter notes
|/
* cff4379 Add copy of serving info
* 04776eb Add baking temperature and time

What to read out of it: the straight line from Module 4 has forked. Two lanes at the top — main's newest commit in the left lane, sourdough's in the right — and the |/ is the fork point rejoining downward at cff4379, the last commit both branches share (their common ancestor — remember the term; Module 6 is built on it). Everything below the fork is shared history, stored once. This little picture is the shape of every real repository you will ever work in, just multiplied.

B3. Switching with uncommitted changes

Official docs: git-switch manual

Must you commit before switching? No — and the actual rule surprises people in both directions. git switch carries uncommitted changes with you whenever it can do so without destroying anything: if the files you touched are identical in both branches, your edits simply remain in the working tree after the switch. It refuses only when switching would overwrite your uncommitted work — when the touched file differs between the branches (or exists in one and not the other). The refusal is a hard stop with exit code 1, and its wording tells you the two legitimate outs: commit the work, or stash it (Module 11 — until then, committing is the answer).

Counter-intuitive: "switching branches" does not give you a clean, separate workspace per branch — there is one working tree, and uncommitted changes float on top of whichever branch you switch to. Engineers regularly discover their half-finished edits "followed them" onto a hotfix branch and got committed there by a hasty commit -a. The working tree belongs to you, not to a branch; only commits belong to branches. (One tree per branch does exist as an opt-in feature — git worktree, Module 11.)
🧪 Exercise 5.5 — a deliberate failure: the blocked switch
bash
cd ~/git-course/kitchen
git switch sourdough
echo "starter: feed daily, keep warm" > starter.txt   # modify a file main doesn't have
git switch main
echo "exit code: $?"
git status -s                 # your edit survived the refusal
git restore starter.txt       # undo the edit (restore is Module 7; trust it this once)
git switch main
Expected result — click to reveal (the first switch fails on purpose)
plain text
Switched to branch 'sourdough'
error: Your local changes to the following files would be overwritten by checkout:
	starter.txt
Please commit your changes or stash them before you switch branches.
Aborting
exit code: 1
 M starter.txt
Switched to branch 'main'

What to read out of it: main has no starter.txt, so switching would have to delete the file — including your uncommitted edit — and Git aborts rather than destroy work: Aborting, nothing changed, edit intact ( M in short status). The error names the exact files at risk and both remedies. Note the word checkout in a message printed by switch — the old command's name still leaks through Git's internals (C2 explains the relationship). Had you instead edited bread.txt — identical on both branches — the switch would have succeeded and carried the edit along: run that variant yourself and watch M bread.txt appear on the other side.

🎯 Interview questions — Part B

🎯 "How do you switch branches in Git?" — asked verbatim in GeeksforGeeks' 70+ Git Interview Questions, updated Jul 2026

git switch <branch> — the modern command (Git ≥ 2.23); git checkout <branch> is the older equivalent still in universal use. git switch -c <branch> creates and switches in one step. Under the hood a switch rewrites HEAD to point at the new branch, then updates the index and working tree to that branch's tip snapshot. Uncommitted changes are carried over when they do not collide with the target branch's content; if they would be overwritten, the switch aborts and asks you to commit or stash first.

The details that separate candidates: an average answer names the command. A strong answer states what actually changes (HEAD, index, working tree — in that causal order) and gets the uncommitted-changes rule right: carried when safe, refused when destructive — most candidates wrongly claim a clean tree is required. Knowing why switch exists (checkout's overload — branch-switching and file-restoring in one command — caused real accidents, so 2.23 split it into switch + restore) turns a syntax answer into a design answer.

Part C — Managing branches

C1. Create-and-switch, rename, delete

The everyday moves, all pointer operations. Create and switch at once: git switch -c <name> (the flow you will actually use — "start work on X" is one command). Rename: git branch -m <old> <new> (or just -m <new> for the current branch). Delete: git branch -d <name> — with a built-in seatbelt: -d refuses to delete a branch whose commits are not reachable from elsewhere, because deleting the last pointer to commits strands them (Module 3: refs are how you find objects). -D overrides the seatbelt when abandoning work is exactly what you want.

🧪 Exercise 5.6 — the lifecycle, including a protective failure
bash
cd ~/git-course/kitchen        # on main
git switch -c focaccia         # create + switch
git switch main
git branch -d focaccia         # delete: fine, it points where main points
git branch -d sourdough        # delete: REFUSED — unmerged commits
echo "exit code: $?"
Expected result — click to reveal (the last delete fails on purpose)
plain text
Switched to a new branch 'focaccia'
Switched to branch 'main'
Deleted branch focaccia (was de08e42).
error: the branch 'sourdough' is not fully merged.
If you are sure you want to delete it, run 'git branch -D sourdough'
exit code: 1

What to read out of it: focaccia deleted silently — it pointed at the same commit as main, so nothing becomes unreachable; the (was de08e42) is Git handing you the hash as it deletes, a last-chance note you could still recreate the branch from. sourdough is different: its commit exists nowhere else, so -d refuses and names the override. Do not run the -D it suggests — we merge sourdough properly in Module 6. The seatbelt's logic is pure Module 3: branches are how commits stay findable; Git guards the last pointer.

C2. checkout — the older command you must still be able to read

Official docs: git-checkout manual

For Git's first fourteen years, one command did double duty: git checkout <branch> switched branches, and git checkout -- <file> overwrote a file from the index — two unrelated dangers sharing a name. Typo a branch name that happens to be a file name, and instead of switching you silently destroyed your edits. Git 2.23 (2019) split the roles: switch for branches, restore for files (Module 7). Checkout remains fully supported and is what you will see in most documentation, Stack Overflow answers, older scripts — and interviews. Read it by argument: branch name → a switch; file path → a restore; commit hash → a detach (Part D). Write switch/restore yourself; translate checkout on sight.

🎯 Interview questions — Part C

🎯 "What is 'git checkout'?" — asked verbatim in GeeksforGeeks' 70+ Git Interview Questions, updated Jul 2026

Historically Git's multipurpose "make my working area look like X" command, with three distinct behaviors selected by argument type: git checkout <branch> switches branches; git checkout <commit> moves HEAD directly to a commit (detached HEAD); git checkout -- <file> overwrites a working-tree file from the index, discarding uncommitted edits. Since Git 2.23 those roles are covered by two focused commands — git switch (branches, detaching) and git restore (files) — introduced precisely because checkout's overload caused accidental data loss. Checkout remains supported and ubiquitous in existing material.

The details that separate candidates: an average answer says "it switches branches." A strong answer enumerates all three argument-dependent behaviors and names the dangerous one (the file form destroys uncommitted work with no confirmation), then tells the design story: the 2.23 split into switch/restore and why. The closing habit that lands well with interviewers: "I write switch and restore; I read checkout" — it signals both modern practice and the ability to work in older codebases and follow older runbooks.

Part D — Detached HEAD, demystified

D1. HEAD pointing at a commit instead of a branch

Module 3 defined the normal state: .git/HEAD contains ref: refs/heads/main — HEAD names a branch. But you can point HEAD directly at a commit: git switch --detach <commit> (or git checkout <commit>). Now .git/HEAD contains a raw hash. That is the entire definition of detached HEAD — no branch is current; you are standing on a commit itself.

Why would you? To look around the past: check out last month's release, run its tests, reproduce a bug against it — a read-only visit needs no branch. The state is only hazardous if you commit while detached: the new commit's only reference is HEAD itself, so the moment you switch away, no ref points at it — it is stranded (reachable only via the reflog, Module 7's safety net, until garbage collection eventually removes it). Git knows this and warns you loudly on the way out, naming the exact rescue command. Read that warning once in safety, below, and detached HEAD stops being a horror story forever.

Real-world analogy — visiting the archive without a bookmark

Normally you read the book at one of your bookmarks, and new pages get added where the bookmark is. Detached HEAD is walking into the archive and opening the book directly to page 300 — perfectly fine for reading. But if you start writing new pages while standing there, they attach after page 300 with no bookmark on them: walk away, and nothing marks where your writing went. The librarian stops you at the door: "you left pages behind — want a bookmark? Here is the exact shelf position." Take the bookmark or accept the loss; both are valid.

Where the analogy stops working. Lost pages in an archive are lost. Git's are not — every commit's hash still opens it directly (git branch rescue <hash> any time you still know the hash), and the reflog (Module 7) remembers where HEAD has been even after you forgot — for unreachable commits like these, roughly 30 days under default housekeeping (reachable entries get ~90). Detached-HEAD commits are unlisted, not destroyed. The real risk is forgetting they exist, not losing them.

🧪 Exercise 5.7 — detach, commit, and read the famous warning calmly

Use your hash for Add kneading method.

bash
cd ~/git-course/kitchen
git switch --detach 9a73162
cat .git/HEAD                  # a raw hash — the definition of detached
git status | head -1
echo "test note" > detached-note.txt
git add detached-note.txt && git commit -m "Note from the past"
git switch main                # leave — and READ the warning
Expected result — click to reveal (ends with the warning, on purpose)
plain text
HEAD is now at 9a73162 Add kneading method
9a73162feb3e3337760f0e93b6a981cfe24c199b
HEAD detached at 9a73162
[detached HEAD 54a651c] Note from the past
 1 file changed, 1 insertion(+)
 create mode 100644 detached-note.txt
Warning: you are leaving 1 commit behind, not connected to
any of your branches:

  54a651c Note from the past

If you want to keep it by creating a new branch, this may be a good time
to do so with:

 git branch <new-branch-name> 54a651c

Switched to branch 'main'

What to read out of it: .git/HEAD holds a bare hash — compare with every previous cat of it. Status says HEAD detached at 9a73162 — a state label, not an error. The commit works normally but its prefix says [detached HEAD …]: no branch moved, because HEAD names none. Then the warning, which is Git at its most helpful: what is being left (1 commit), why it matters (not connected to any of your branches), and the exact rescue command with the hash filled in. Run the rescue if you want (git branch time-capsule 54a651c — your hash) or let the commit fade; either choice is correct, and that is the lesson: detached HEAD plus warning-literacy equals a tool, not a trap.

Now imagine this at 500 hosts. CI systems live in detached HEAD: build agents check out an exact commit hash — not a branch — so the build is reproducible and cannot be moved by a concurrent push. Every pipeline log that says HEAD detached at <hash> is this module's Part D running in production, deliberately. Fleet corollary: automation that must commit (version bumps, changelog bots) must first create or switch to a real branch, or its commits strand exactly as in Exercise 5.7 — a bug class seen in homegrown CI scripts everywhere.

🎯 Interview questions — Part D

🎯 "What is a detached HEAD state?" — asked verbatim in GeeksforGeeks (updated Jul 2026) and InterviewDrill's Git & GitHub Interview Questions, May 2026

Detached HEAD is when .git/HEAD holds a commit hash directly instead of a branch reference — you are "on" a commit, not a branch. You enter it by checking out anything that is not a branch name: a hash, a tag, HEAD~2. It is fully legitimate for inspection: examining an old release, bisecting (Module 13), CI builds pinned to exact commits. The one consequence: commits made while detached advance no branch, so after switching away they are referenced by nothing — Git prints a warning with the rescue command (git branch <name> <hash>), and the reflog keeps them findable for a grace period before garbage collection.

The details that separate candidates: an average answer says "HEAD points to a commit instead of a branch, it's dangerous." A strong answer drops the danger framing and replaces it with the precise failure mode (only committing then leaving strands work — visiting is free), the recovery paths (the exit warning's git branch command; reflog afterwards), and the legitimate uses — naming CI's pinned-hash checkouts marks you as someone who has read pipeline logs. Precision bonus: checking out a tag also detaches, because tags don't move — a case most candidates have experienced but never explained.

Part E — Production practice

E1. Symptom → cause → diagnosis → fix

Click the symptom you're seeing.

⚠️ "My file disappeared after switching branches!"

What is really happening: The working tree now shows the other branch's snapshot, which never contained the file.

Diagnose: git log --oneline --all -- <file> · git show <branch>:<file>

The fix: Nothing is lost — switch back, or read it from the other branch by name.

⚠️ error: Your local changes ... would be overwritten by checkout ... Aborting

What is really happening: Uncommitted edits collide with the target branch's content; Git refuses to destroy them.

Diagnose: git status -s to see the colliding files

The fix: Commit them, or stash (Module 11); then switch.

⚠️ Half-finished edits "followed" you onto another branch and got committed there

What is really happening: Non-colliding uncommitted changes are carried across switches — one working tree, floating edits.

Diagnose: git log --stat -1 on the polluted branch

The fix: Undo/move the commit (Modules 7 and 10); the habit fix is git status before every switch and every commit.

⚠️ error: the branch 'X' is not fully merged.

What is really happening: This is -d's seatbelt: deleting this pointer would strand commits reachable nowhere else.

Diagnose: git log --oneline main..X — see exactly what would strand

The fix: Merge it first (Module 6), or -D if abandoning the work is intended.

⚠️ git status says HEAD detached at <hash> and the team chat says panic

What is really happening: HEAD points at a commit, not a branch — someone checked out a hash or tag; it is a state, not an error.

Diagnose: git status · git log --oneline -1

The fix: Just looking? git switch <branch> when done. Committed here? git branch rescue-work first, then switch.

⚠️ Committed to the wrong branch (e.g. straight to main instead of a feature branch)

What is really happening: HEAD named a different branch than you assumed when you committed.

Diagnose: git log --oneline -3 · git branch -v

The fix: Run git branch feature to capture the commit, then move main back (Module 7's reset); prompt-with-branch-name in your shell prevents recurrence.

E2. Capstone — four tickets

Ticket 1 — "Hotfix needed, but I'm mid-feature." Production is down; the fix is one line. You are on feature/checkout-flow with six modified files you are not ready to commit. Walk the exact sequence.

Worked answer: first, know your options honestly. If the six files do not collide with main's content, git switch main simply carries them (B3) — but carried edits will pollute the hotfix if you then use commit -a, so the disciplined route is: park the work-in-progress with a temporary commit (git add -A && git commit -m "WIP: checkout flow — do not push"), then git switch main, git switch -c hotfix/timeout, make the one-line fix, commit properly, and (after Module 8) push it for deploy. Return with git switch feature/checkout-flow; undo the WIP commit while keeping the files (git reset --soft HEAD~1 — Module 7 teaches it; note it now) and continue. Module 11's stash makes the parking step one command — this ticket is re-solved there. What the interviewer listens for: you never destroyed the WIP, you never let it leak into the hotfix, and every step is reversible.

Ticket 2 — "CI says 'detached HEAD' — is the pipeline broken?" A junior engineer escalates: every build log begins HEAD detached at 3fa9c21 and they believe the runner is misconfigured.

Worked answer: working as intended, and worth explaining well. CI checks out the exact commit being tested — not a branch — so the build is pinned: a teammate pushing mid-build cannot change what this run tests, and re-running the job months later builds bit-identical input (Module 3: a hash names an immutable snapshot). Detached HEAD is the natural state of a machine that only reads. The one real rule to add to the runbook: any pipeline step that must create commits (version bump, changelog) must first git switch -c a real branch, or its commit strands on the detached HEAD — cite Exercise 5.7's warning as the failure signature to grep for in logs when a bot's commits "vanish."

Ticket 3 — "We deleted the branch — is the release commit gone?" A release branch was deleted with -D during cleanup; its final commit hash 81f3d02 is still in the deploy log. Management asks if the code is recoverable.

Worked answer: almost certainly yes, and instantly. Deleting a branch deletes a 41-byte pointer, not commits (Module 3). If any clone still has the hash reachable — or the deploy log preserves it, as here — recovery is one command: git branch release-restored 81f3d02 (verify first with git cat-file -t 81f3d02commit). Time matters mildly: unreachable commits are garbage-collected eventually (default grace measured in weeks, Module 15), so recover now, not next quarter. And the systemic fix: releases should be tagged (Module 12) — tags exist precisely so that "cleanup" cannot orphan a shipped snapshot. Push the recovered branch (Module 8) so it exists somewhere durable.

Ticket 4 — "Standardize our branch names." The team's branch list is chaos: fix, johns-stuff, new, test2. Propose a convention and the migration.

Worked answer: propose type-prefixed, slash-namespaced names: feature/<ticket>-<slug>, bugfix/<ticket>-<slug>, hotfix/<slug>, release/<version> — slashes group branches like directories in every tool (and in .git/refs/heads/feature/..., literally directories, as Module 3 predicts). Rules worth writing down: names describe work, not people (branches outlive assignment); include the ticket ID so tooling can cross-link; delete on merge (the PR flow in Module 9 automates this). Migration is cheap because renames are pointer moves: git branch -m johns-stuff feature/PAY-231-refund-retry — with the caveat that a branch already shared on the server needs its remote counterpart renamed too (Module 8's push/delete dance; sequence it after that module lands in the team). Enforcement: a server-side hook or host branch-protection rule (Modules 9/15) rejecting non-conforming names — conventions without enforcement decay in one sprint.

E3. Documentation reference

TopicOfficial sourceWhat it covers
Branch conceptGit Book §3.1 — Branches in a NutshellPointers, HEAD, switching — with diagrams
git branchgit-branch manualList/create/rename/delete, -v, -d vs -D
git switchgit-switch manualSwitching, -c, --detach, carry/refuse rules
git checkoutgit-checkout manualThe legacy multitool: all three argument forms
Branching + merging walkthroughGit Book §3.2 — Basic Branching and MergingThe workflow this module sets up and Module 6 completes

E4. Self-assessment

Try each question aloud, from memory, before opening its answer — the gap between your answer and the hidden one is what to study.

1. What physically is a branch, and what is the total cost of creating one?

A branch is a named, movable pointer to a commit — a ~41-byte text file in .git/refs/heads/ holding one commit hash. Creating one (git branch <name>) writes one more 41-byte file pointing at the commit you are on; no files are copied and nothing about your project changes. The operation is O(1) regardless of repository size, which is why Git users branch for everything.

2. What three things does git switch <branch> change, in causal order?

First it rewrites .git/HEAD to ref: refs/heads/<branch>; then it resets the index to that branch's tip commit; then it updates the working tree to that commit's snapshot. From that moment on, commits advance the new branch — that is all "being on a branch" means.

3. You commit while on branch X. Which pointers move — HEAD, X, both, neither?

Only X's pointer is rewritten: the commit advances the branch HEAD names. .git/HEAD itself still contains ref: refs/heads/X, unchanged — it follows along because it names X, not because it moved. No other branch is touched by anything you do here, which is the entire safety model of branch-based work.

4. A file "disappears" after switching. Where is it, and what two commands prove it without switching back?

It is safe in the object database, on the other branch — the working tree always displays the current branch's snapshot, which never contained the file. Prove it from where you stand with git log --oneline <branch> (the commit is there) and git show <branch>:<file> (read the file's content by branch name). The working tree is a view, not the data.

5. When does switching carry uncommitted changes, and when does it refuse? What are the two remedies the refusal names?

Git carries uncommitted changes across a switch whenever it can do so without destroying anything — when the touched files are identical in both branches. It refuses (hard stop, exit code 1) when switching would overwrite your work: the touched file differs between the branches or exists in only one. The refusal names the two outs: commit the work, or stash it (Module 11).

6. Why does git branch -d sometimes refuse, and what does the refusal protect (in object-database terms)?

-d refuses to delete a branch whose commits are not reachable from anywhere else, because deleting the last pointer to commits strands them — refs are how you find objects, and unreferenced commits are eventually garbage-collected. The seatbelt guards the last pointer; -D overrides it when abandoning the work is exactly what you want.

7. What are the three argument-dependent behaviors of git checkout, and which two commands replaced them?

Branch name → a switch; commit hash → a detach (HEAD moves directly onto the commit); -- <file> → a restore, overwriting the working-tree file from the index and destroying uncommitted edits. Git 2.23 split the roles into git switch (branches, detaching) and git restore (files). Write the modern forms; translate checkout on sight.

8. Define detached HEAD by the contents of one file. Is entering it dangerous? Is committing in it?

Detached HEAD is when .git/HEAD contains a raw commit hash instead of ref: refs/heads/<branch>. Entering it is not dangerous — visiting the past to look around is exactly what it is for. Committing is the hazard: the new commit's only reference is HEAD itself, so the moment you switch away no ref points at it and it is stranded (findable only via the reflog until garbage collection).

9. Recite the rescue for a commit made on a detached HEAD, before and after you switch away.

Before switching away: plant a branch while standing on the commit — git branch <name> captures it. After switching away: Git's exit warning hands you the exact command with the hash filled in — git branch <new-branch-name> <hash> — and it works any time you still know the hash. The reflog (Module 7) keeps such unreachable commits findable for roughly 30 days under default housekeeping.

10. Why do CI systems deliberately run in detached HEAD?

Build agents check out an exact commit hash — not a branch — so the build is reproducible and cannot be moved by a concurrent push; a hash names an immutable snapshot. Detached HEAD is the natural state of a machine that only reads. The corollary: automation that must commit (version bumps, changelog bots) must first create or switch to a real branch, or its commits strand exactly as in Exercise 5.7.

11. What is the common ancestor in Exercise 5.4's graph, and why will Module 6 care about it?

It is cff4379 “Add copy of serving info” — the fork point where |/ rejoins downward, the last commit both branches share. Everything below it is shared history, stored once. Module 6 is built on it: the common ancestor is the merge base that makes merging a computation over what each side changed since the fork.

12. --all on git log: what does it add, and what would you wrongly conclude without it?

--all shows commits reachable from every branch, not just HEAD's. Log's default start point is HEAD, so without --all the other branch's commits are invisible — and you would wrongly conclude they are gone, when they are simply not reachable from where you are standing.

E5. Sources

Interview questions in this module were captured verbatim from: GeeksforGeeks — Top 70+ Git Interview Questions (updated Jul 30, 2026) and InterviewDrill — Git & GitHub Interview Questions 2026 (May 12, 2026). The published corpus on branching skews heavily toward merge/workflow questions (covered in Modules 6 and 9); creation/rename/delete mechanics appear rarely as standalone questions, so Parts A–D carry the four closest published ones rather than invented extras. Technical claims were verified against the official Git documentation in E3; every command and output on this page was executed on Git 2.43.0 on Linux, continuing the kitchen repository. Commit hashes in outputs will differ on your machine.

🗒️ Cheat sheet — Module 5

CommandWhat it does
git branch · git branch -vList branches (* = current) · with each tip's hash and subject
git branch <name>Create a branch at the current commit (does not switch)
git switch <branch> · git switch -c <name>Switch to a branch · create and switch in one step
git branch -m <old> <new>Rename a branch (pointer move)
git branch -d <name> · -DDelete if its commits are reachable elsewhere · force-delete regardless
git switch --detach <commit>Point HEAD directly at a commit (detached HEAD) for inspection
git branch <name> <hash>Plant a branch on any commit — the detached-HEAD rescue
git log --oneline --graph --allThe whole fork structure, all branches
git checkout <branch> / <commit> / -- <file>Legacy: switch / detach / restore file — read on sight, write the modern forms
git show <branch>:<file>Read a file from another branch without switching (Module 4 skill, branch-flavored)

Key concepts: branch = movable 41-byte pointer; creating one copies nothing and is O(1) · committing moves only the branch HEAD names · the working tree is a view of the current branch's snapshot — "disappearing" files are on the other branch, safe in the object database · uncommitted changes float over switches: carried when safe, refused when they would be overwritten — there is one working tree, and it belongs to you, not to a branch · divergence = two children of a common ancestor; --graph --all shows the fork · -d refuses to strand unreachable commits; -D overrides · checkout = switch + restore + detach in one legacy command · detached HEAD = .git/HEAD holds a raw hash; visiting is free, committing-then-leaving strands work, and the exit warning hands you the rescue.

Next: Module 6 — Mergingmain and sourdough have diverged from a common ancestor, and you left them that way on purpose. Module 6 reunites them: fast-forward vs three-way merges, why conflicts happen mechanically, and how to resolve them without fear.
Spotted a mistake or want something added? Send me a note.