skip to content

questions

6

What does git diff show by default, and how does git diff --staged differ?

level: juniorimportance: must knowfreq 85%

answer

  1. Diff always compares a pair
  2. Three snapshots, three gaps
  3. Staged changes vanish from bare diff
  4. --staged is index versus HEAD
  5. --cached is the same flag

basics

~20 s

Plain git diff compares the working tree against the index, showing changes you have not staged yet. git diff --staged (identical to --cached) compares the index against HEAD, showing exactly what the next commit would contain.

solid answer

~50 s

Every `git diff` invocation compares **two** snapshots, and the default pair is the one people forget. Bare `git diff` compares the **working tree** to the **index** (the staging area) — it answers "what have I changed but not yet staged?". `git diff --staged` (`--cached` is the same flag under a different name) compares the **index** to **HEAD** — "what would I commit if I ran git commit right now?". `git diff HEAD` skips the middle and compares the working tree directly to HEAD, so it shows staged and unstaged changes together. None of the three shows untracked files: an untracked file has no entry in the index, so there is nothing to diff against — `git status` is what surfaces it. Add `-- <path>` to any of them to limit the comparison to a path.

go deeper

for a junior

Memorize the pair each form compares: git diff is working tree versus index, git diff --staged is index versus HEAD. Be able to say why staged changes disappear from bare git diff output.

for a middle

Explain the index as a real tree stored in .git, and derive all three forms from the two-snapshot rule rather than reciting them. Know that untracked files have no index entry to diff against.

for a senior

Show a review habit: stage deliberately, then read git diff --staged as the last gate before committing. Be ready to explain git add -N and pathspec disambiguation with --.

for a principal

Frame it as team hygiene: whether people review the staged patch before committing determines how much noise reaches review. Argue for conventions that keep commits reviewable rather than for tooling that hides the index.

## Why "what does git diff show" is the wrong question `git diff` never shows "the changes" in the abstract. It always shows the difference between **two specific snapshots of the tree**, and the whole skill is knowing which two a given invocation picked. Git keeps three snapshots in play at any moment: - **HEAD** — the commit you last checked out or created; the last recorded state. - **The index** (also called the staging area or cache) — a file inside `.git` that holds the exact tree your *next* commit will record. - **The working tree** — the files on disk you actually edit. A change normally travels working tree → index → HEAD, and `git diff` gives you one form per gap. ## The three everyday forms **`git diff`** — working tree versus index. Output is the set of edits you have made but not staged. Once you `git add` a file, its changes disappear from this output, which surprises beginners who think `git diff` shows "everything I changed". **`git diff --staged`** — index versus HEAD. This is the pre-commit review: exactly the patch that `git commit` will record, no more and no less. `--cached` is an older spelling of the same option and behaves identically; there is no difference to memorize, only two names for one flag. **`git diff HEAD`** — working tree versus HEAD, ignoring the index boundary. This is "everything I have changed since the last commit, staged or not". It is the closest thing to the intuitive meaning of "show me my changes". A useful invariant: the patch from `git diff` plus the patch from `git diff --staged` together account for the same content as `git diff HEAD` (they compose, even though patches do not literally concatenate). ## Comparing against a commit or a branch Giving `git diff` one commit argument (`git diff main`) compares the **working tree** to that commit. Giving it two (`git diff main feature`, or the equivalent `git diff main..feature`) compares the two commits' trees and takes the working tree out of the picture entirely. Adding `--staged` with a commit — `git diff --staged main` — compares the index to that commit instead of to HEAD. ## What git diff never shows - **Untracked files.** They are absent from both the index and HEAD, so there is no "before" side. `git status` lists them; `git add -N <path>` (intent-to-add) creates an empty index entry so that subsequent `git diff` output includes the file as an addition. - **Ignored files**, for the same reason. - **The commit message or metadata** — that is `git show`'s job. - **Changes on the remote.** `git diff` compares local snapshots only; comparing against a remote-tracking branch requires naming it explicitly, e.g. `git diff origin/main`. ## Narrowing the comparison Any form accepts a pathspec after `--`: `git diff -- src/`, `git diff --staged -- pom.xml`. The `--` separator matters when a path and a branch share a name — without it Git cannot tell whether `main` means the branch or the file. Options such as `--stat`, `--name-only` and `-w` apply to every form identically, because they change how the comparison is *rendered*, not which two snapshots are compared. ## Practical habits Reviewing your own change before committing is a two-step ritual: run `git diff` to see what you have not staged yet, stage deliberately, then run `git diff --staged` as the final read of the patch you are about to record. Treating `git diff --staged` as the last gate catches debug prints, stray whitespace and accidentally staged files far more reliably than reading `git status`, which reports only filenames and not content. A related trap: `git diff` with no arguments in a repository where everything is staged prints nothing at all, and people conclude "Git lost my changes". Nothing is lost — the changes moved one snapshot to the right, and `git diff --staged` or `git diff HEAD` will show them. ## Outside the repository `git diff --no-index <fileA> <fileB>` compares two arbitrary paths using Git's diff engine even when they are not tracked (or not even inside a repository), which is handy for comparing generated output with Git's colouring and options.

  • What does git diff HEAD show that the other two forms do not?
    It compares the working tree directly to the last commit, so staged and unstaged edits appear in one patch. Use it when you want the total change since the last commit and do not care which side of the index each edit currently sits on. It still omits untracked files.
  • Why can git diff print nothing while git status shows modified content?
    Because everything you changed is already staged: bare git diff compares the working tree to the index, and those two now match. Run git diff --staged or git diff HEAD to see the change. The same emptiness appears when the only differences are untracked files.
  • How do you make git diff show a file that Git is not tracking yet?
    Run git add -N <path> (intent-to-add). That records an empty index entry without staging content, so the file becomes part of the working-tree-versus-index comparison and shows up as an addition in subsequent git diff output, including in git add -p.
  • How do you limit a diff to one directory without ambiguity?
    Put the pathspec after a double dash: git diff -- src/. The -- separator tells Git that everything after it is a path, which matters when a file and a branch share a name; without it Git may resolve the argument as a revision instead.

saying these in an interview costs you the question

  • Thinks bare git diff shows staged changes too
  • Believes git diff lists untracked files
  • Assumes --staged and --cached behave differently
  • Thinks git diff compares against the remote branch
  • Says changes are lost when git diff prints nothing

context

open as a page

What does git show display for a commit, and how does it differ from git diff?

level: juniorimportance: should knowfreq 45%

basics

~20 s

git show prints one object: for a commit it prints the metadata and log message plus the patch against its first parent. git diff prints only a patch between two snapshots and never shows commit metadata.

open as a page

Which git diff options help you summarize or filter a very large diff?

level: middleimportance: should knowfreq 50%

basics

~20 s

Use --stat or --shortstat for a per-file summary, --name-only or --name-status to list touched paths, --diff-filter to keep only additions, deletions or renames, -w to hide whitespace-only noise, and --word-diff for prose where line diffs read badly.

open as a page

In git diff, what does A...B compare that A..B does not?

level: middleimportance: should knowfreq 55%

basics

~20 s

git diff A..B compares the two commits' trees directly, identical to git diff A B. git diff A...B compares the merge base of A and B against B, so it shows only what B added, ignoring changes made on A since they diverged.

open as a page

What do Git's histogram and patience diff algorithms do that the default does not?

level: middleimportance: nice to knowfreq 28%

basics

~20 s

Both anchor the diff on lines that appear rarely, so hunks line up with meaningful code instead of sliding onto common lines like closing braces. Git's default is Myers; select others with git diff --histogram, --patience, or the diff.algorithm config key.

open as a page

What does git range-diff show, and when is it the right tool?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

git range-diff compares two versions of the same patch series — typically a branch before and after a rebase or rewrite — pairing up commits and showing a diff of their diffs, so you can see what actually changed between iterations.

open as a page