Lesson 9 of 30
git rebase -i HEAD~N hands you an editable todo list of your last N commits — squash, fixup, reword, drop, reorder — before anyone else ever sees them.
16 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
A regular git rebase main replays your commits verbatim onto a new base. git rebase -i HEAD~N does the same replay, but first opens an editable todo list of the last N commits and lets you decide what happens to each one before the replay runs.
This is the standard tool for cleaning up a messy local history before opening a PR — the "wip", "fix typo", "actually fix typo", "why doesn't this work" commits you made while actually solving the problem, squashed down into commits that tell a reviewer a coherent story instead of narrating your debugging session. Every commit in the todo list gets a one-word action: pick keeps it as-is, reword keeps the changes but lets you edit the message, squash merges it into the commit above and lets you combine the messages, fixup merges it into the commit above and silently discards its message, drop removes it entirely, and edit pauses the rebase at that commit so you can amend it — even split it into more than one.
The mechanism underneath is identical to a plain rebase: Git still replays each surviving commit as a new object with a new hash. The interactive part is only the todo list controlling which commits get replayed and how — squashing two commits just means Git combines their diffs into a single new commit instead of creating two.
$ git log --oneline
f4a5b6c fix typo in login handler
e3f4a5b wip: add login validation
d2e3f4a add login form
c1d2e3f initial commit
$ git rebase -i HEAD~3
That opens an editor with the todo list, oldest commit first:
pick d2e3f4a add login form
pick e3f4a5b wip: add login validation
pick f4a5b6c fix typo in login handler
# Rebase c1d2e3f..f4a5b6c onto c1d2e3f (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but discard this commit's log message
# d, drop <commit> = remove commit
Edit the lines to fold the wip commit and typo fix into the form commit, and clean up the message:
pick d2e3f4a add login form
fixup e3f4a5b wip: add login validation
fixup f4a5b6c fix typo in login handler
Save and quit. Git replays: applies add login form, then folds in the diffs from the two fixup commits without asking for message input (since fixup discards their messages), producing one clean commit:
$ git rebase -i HEAD~3
Successfully rebased and updated refs/heads/feature.
$ git log --oneline
a5b6c7d add login form
c1d2e3f initial commit
Three commits became one — a5b6c7d is a brand-new object, none of the original three still exist on this branch.
git log's newest-first convention — reordering lines in the editor genuinely reorders commit application, and can cause conflicts if a later commit depended on an earlier one you moved past it.fixup/squash only fold into the commit directly above them in the list, not into whatever commit you "meant." If you reorder lines without also moving the fixup line to sit right after its intended target, it folds into the wrong commit.This is the previous rebase lesson with one addition: instead of replaying every commit unconditionally, the todo list is a checkpoint before each replay step where you can reword, merge, or discard. Nothing about the underlying commit-is-an-immutable-hashed-object model changes — you're just exercising more control over which new objects get created.