interview prep

The git cheat sheet that starts with reflog

The command that has saved me the most times isn’t in most cheat sheets: git reflog. Someone runs git reset --hard on the wrong branch, watches an afternoon of work disappear from the working tree, and assumes it’s gone for good. It almost never is. Git kept a record of where that branch pointed a minute ago, and reflog will show it to you.

Most git confusion clears up the moment you stop thinking in files and start thinking in pointers.

What git is actually storing

A commit is a full snapshot of your tree, not a diff, identified by a hash of its contents. A branch is a movable pointer to one commit. HEAD is a pointer to the branch you have checked out. That’s close to the whole data model. Once branches read as cheap labels and commits read as permanent objects, most operations stop feeling dangerous. You are rarely deleting work. You are moving a label from one commit to another, and the old commit is still sitting in the object database.

The reflog is what makes that concrete. Every time HEAD or a branch moves, git appends a line to the reflog recording the old position. Those entries stick around for about 90 days for reachable commits and 30 days for ones nothing points to, controlled by gc.reflogExpire. A “lost” commit inside that window is a lookup, not a disaster.

The three places a change can live

Edit a file and it changes in the working tree. Run git add and git copies that version into the staging area, also called the index. Run git commit and git turns the index into a new commit. This is why git status can list the same file under both “Changes to be committed” and “Changes not staged for commit” at once: you staged it, then edited it again, so the index holds one version and the working tree holds a newer one. Getting asked to explain that in a pairing round is common, and it’s the answer people most often fumble.

The commands worth keeping close

This is the set I actually reach for in a normal week, with the traps that bite people who copy a command without reading it.

What you want to do Command Watch out for
See what’s staged versus not git status -s, git diff, git diff --staged Plain diff hides staged changes; add --staged to see what a commit would include
Stage part of a file git add -p Splits a sprawling change into clean, reviewable commits
Undo the last commit, keep the changes git reset --soft HEAD~1 Moves the branch back and leaves the work staged; --hard would delete it
Unstage a file git restore --staged file Keeps your edits; only removes it from the index
Discard edits to a file git restore file Destroys uncommitted changes to that file with no reflog to recover them
Undo a commit that’s already pushed git revert a1b2c3d Adds a new inverse commit; safe on branches other people share
Recover a commit after a bad reset git reflog, then git switch -c save HEAD@{2} Reflog entries last about 90 days
Replay your branch on the latest main git rebase main Don’t rebase a branch other people have already pulled
Push after rewriting local history git push --force-with-lease Refuses the push if someone else pushed since your last fetch
Clean up recent commits git rebase -i HEAD~4 Mark lines reword, squash, or fixup in the editor
Copy one commit to your branch git cherry-pick a1b2c3d Handy for hotfixes; can duplicate the change if you later merge the source
Shelve work in progress git stash push -m "wip", git stash pop Pop can conflict, and forgotten stashes pile up silently
Work two branches at once git worktree add ../hotfix main A second working directory on the same repo, no stashing needed
Find the commit that broke a test git bisect start, git bisect good, git bisect bad Binary search across history; automate it with git bisect run
See who last changed a line git blame -L 10,20 file Pair with git log -p to read the reasoning behind the change

reset, revert, and restore do three different jobs

These three get mixed up more than anything else in git, and interviewers like the question because the wrong choice on a shared branch causes real damage.

git reset moves your current branch pointer to a different commit. With --soft the changes from the removed commits stay staged, with --mixed (the default) they stay in the working tree unstaged, and with --hard they are wiped from the working tree. Reset rewrites where your branch points, so keep it for history that only lives on your machine.

git revert never rewrites anything. It creates a new commit that undoes an earlier one, which makes it the right tool for a commit you already pushed and others have pulled.

git restore is the newer, narrower command for files. git restore file throws away working-tree edits, and git restore --staged file unstages a file without touching your edits. It took over the file-level jobs that git checkout and git reset used to overload, which is why older answers online still use checkout for this.

Getting work back after a bad command

Here is the move that recovers a hard reset. Say you meant to drop one commit and dropped three:

$ git reset --hard HEAD~3         # meant to undo one, undid three
$ git reflog
a1b2c3d HEAD@{0}: reset: moving to HEAD~3
9f8e7d6 HEAD@{1}: commit: add retry to the upload path
1122eef HEAD@{2}: commit: fix flaky timeout test
$ git switch -c recovered 9f8e7d6

The reflog lists every position HEAD held, newest first. Find the line from just before the mistake, then point a fresh branch at that hash. The same trick undoes a botched rebase or a bad merge: reflog back to the state before it, and branch off that hash. The only work reflog cannot bring back is edits you never committed or staged, because those were never written to an object. That is the real argument for committing early and often, even when the messages are rough.

Rewriting history you haven’t shared

Interactive rebase is how you turn a messy local branch into something reviewable. git rebase -i HEAD~5 opens the last five commits in an editor where you mark lines to reword, squash, or drop. The cleaner path for fixups is to commit against a specific earlier commit and let git slot it into place:

$ git commit --fixup a1b2c3d
$ git rebase -i --autosquash HEAD~6

The rule that keeps this safe: rebase and amend only on commits nobody else has based work on. When you do rewrite a branch you already pushed, push with git push --force-with-lease instead of plain --force. Force-with-lease refuses the push if someone else added commits since you last fetched, so you overwrite your own outdated view rather than their work.

stash, worktree, and the interrupted task

git stash shelves half-finished work so you can switch context, and git stash pop brings it back. The catch is that pop can conflict, and a stash you forget about stays invisible until you run git stash list. For anything longer than a quick interruption, git worktree add ../hotfix main is the better answer. It checks out a second branch into its own directory backed by the same repo, so you can build and test an urgent fix without stashing or disturbing the branch you were on. When someone asks how you would handle a production emergency mid-feature, worktree is a sharper answer than “stash everything and hope nothing breaks.”

The engineer who stays calm during a git mess isn’t smarter about git. They just know the object database keeps almost everything, and that git reflog is the first place to look before assuming any commit is gone.

newsletter

What's actually being asked right now

Interview patterns & comp trends, straight to your inbox.

No spam. Unsubscribe anytime.

newsletter

What's actually being asked right now

Interview patterns & comp trends, straight to your inbox.

No spam. Unsubscribe anytime.

1972 Soviet postage stamp commemorating the Mars 2 probe

worth a read

Mars For The Rest of Us — a weekly-or-more deep dive on the technical side of Mars exploration: rocket propulsion, microbiology, mission architecture, and everything in between. Written by Maciej Ceglowski.

Read it on Substack →
Scroll to Top