skip to content

In Git, what is the difference between reset --soft, --mixed, and --hard?

level: middleimportance: must knowfreq 82%

answer

  1. Three trees, three depths
  2. One flag stops early, one goes all the way
  3. Branch pointer always moves
  4. Only one mode touches files on disk
  5. --mixed is the default

basics

~20 s

All three move the current branch pointer to the target commit. --soft stops there, leaving the index and working tree untouched; --mixed also resets the index; --hard additionally overwrites the working tree, destroying uncommitted changes.

solid answer

~40 s

`git reset <commit>` does up to three things, and the flag says how far it goes. It always moves the current branch (and therefore HEAD) to `<commit>`. `--soft` stops there: the index and working tree are untouched, so everything from the discarded commits is left staged — that is why `git reset --soft HEAD~3` followed by `git commit` squashes three commits into one. `--mixed`, the default, also rewrites the index to match the target, so those changes stay in the working tree but unstaged. `--hard` goes all the way and overwrites the working tree too, discarding uncommitted modifications permanently. Only `--hard` destroys work you never committed; untracked files survive even that, since removing them is `git clean`'s job. Commits left behind are still reachable through the reflog and `ORIG_HEAD`.

code

bash · 9 lines
bash
# squash the last three commits into one
git reset --soft HEAD~3
git commit -m "feat(auth): add refresh token rotation"

# undo the last commit but keep its edits unstaged
git reset HEAD~1        # --mixed is the default

# throw the branch and the working tree back to the remote tip
git reset --hard origin/main

go deeper

for a junior

Memorise the ladder: soft touches only the branch pointer, mixed also clears the staging area, hard also rewrites your files. Know that hard is the one that loses uncommitted work.

for a middle

Be ready to narrate reset as three ordered steps over HEAD, index, and working tree, and to show the squash idiom with --soft plus a fresh commit.

for a senior

Show operational judgment: stash or commit before a --hard, know that untracked files survive it, and explain why discarded commits are still recoverable while discarded edits are not.

for a principal

Frame it as a team risk question — which reset modes belong in shared runbooks and aliases, and when a revert-style forward fix is the correct choice over rewinding a branch at all.

## The three trees Every `git reset` question is really a question about which of Git's three snapshots changes. - **HEAD** — a pointer to the commit you are on. Normally HEAD is symbolic: it names a branch such as `refs/heads/main`, and the branch names the commit. "Moving HEAD" with reset therefore means moving the branch. - **The index** (staging area, stored in `.git/index`) — a complete snapshot of what the *next* commit would contain. `git add` copies working-tree content into it. - **The working tree** — the files on disk that you edit. ## What reset does, in three steps `git reset [<mode>] <commit>` performs up to three steps in order: 1. Point the current branch at `<commit>`. This step always happens (and it is the only step `--soft` performs). 2. Make the index match `<commit>`. Happens for `--mixed` and `--hard`. 3. Make the working tree match the index. Happens only for `--hard`. So the modes are cumulative: `--soft` ⊂ `--mixed` ⊂ `--hard`. `--mixed` is the default when you write `git reset <commit>` with no mode. ## --soft Only the branch pointer moves. Because the index still holds the newer content while HEAD now points at an older commit, `git status` shows all the differences as *staged*. This makes `--soft` the standard squash tool: `git reset --soft HEAD~3` then `git commit -m "one message"` collapses the last three commits into a single new commit with identical content. ## --mixed The branch moves and the index is rewritten from the target commit. The working tree is untouched, so the same differences reappear as *unstaged* changes. Two everyday uses: `git reset HEAD~1` to undo the last commit but keep its edits for reworking, and a bare `git reset` (target defaults to HEAD) to unstage everything at once. ## --hard The branch moves, the index is rewritten, and the working tree is forced to match. Any modification you had not committed is gone — there is no object in the database holding it, so no reflog or `git fsck` can bring it back. That is the one genuinely destructive mode. Two nuances people miss: - **Untracked files are not deleted** by `--hard`. Files Git never knew about stay on disk; removing those is `git clean`. - **Commits are not destroyed.** `git reset --hard HEAD~5` leaves those five commits in the object database, still listed in the reflog, until garbage collection eventually prunes unreachable objects. ## The path-limited form `git reset <commit> -- <path>` is a different operation: it copies the named paths from `<commit>` into the index and **never moves the branch pointer**. `--soft` and `--hard` are not accepted with paths. This is the old spelling of "unstage this file"; modern Git prints `git restore --staged <file>` in `git status` instead. ## Safety net Before a reset moves your branch, Git records the previous tip in `ORIG_HEAD`, and the move is appended to the branch's reflog. Recovering a mistaken reset therefore means pointing the branch back at the old commit — but only commits are covered. Uncommitted edits wiped by `--hard` were never objects and are unrecoverable, which is why `git stash` or a throwaway commit is worth the two seconds before a risky reset. ## Other modes `--keep` resets the branch and index but refuses if a file that differs between the two commits has local modifications, so it aborts instead of clobbering. `--merge` is similar and is mainly used to bail out of a conflicted merge. Both are safer middle grounds that senior candidates sometimes mention; `--soft`, `--mixed`, and `--hard` are what interviews actually test.

  • How would you undo a reset that moved your branch to the wrong commit?
    Point the branch back at its old tip. Git writes the pre-reset position to `ORIG_HEAD` and appends the move to the branch's reflog, so `git reset --hard ORIG_HEAD` or a reset to the reflog position restores it. This recovers commits only — working-tree edits destroyed by `--hard` were never stored as objects and are gone.
  • What does git reset <commit> -- <path> do differently?
    With a pathspec, reset never moves the branch pointer. It only copies the named paths out of `<commit>` into the index, leaving the working tree alone — the classic way to unstage a file. `--soft` and `--hard` are rejected in this form, since there is no branch move and no working-tree step to perform.
  • Which mode would you use to squash the last three commits?
    `git reset --soft HEAD~3` followed by `git commit`. The branch rewinds three commits while the index keeps the final content, so a single new commit reproduces exactly the tree you had. `--mixed` would work too but forces you to re-add everything first, and `--hard` would throw the content away.

saying these in an interview costs you the question

  • Claiming reset --hard deletes the commits from the database
  • Saying --soft leaves the branch pointer where it was
  • Thinking reset --hard also removes untracked files
  • Believing --mixed unstages by discarding working-tree edits
  • Assuming git reset <path> moves the branch too

context