Git interview questions and answers

26 Git questions from real DevOps and engineering interviews, sorted by seniority: merge against rebase, reset against revert, the reflog, detached HEAD, and recovering work you thought you had lost.

26 questions6 junior12 mid8 seniorLive exerciseWhat each level testsHow to prepare

All 26 questions

  1. 1

    What is a Git commit, and what is a Git branch?

    A commit is an immutable snapshot of the whole tree, plus metadata: author, message, timestamp, and one or more parent commit hashes. It is not a diff. Git computes diffs on demand by comparing snapshots.

    A branch is a forty-byte file containing a commit hash. That is all. git branch feature writes one file; it copies nothing.

    That is why branching in Git is instant while it was expensive in older systems, and why the chain of parent pointers is the history rather than the branch names.

    HEAD is a pointer to the branch you are on, which is in turn a pointer to a commit. Committing moves the branch pointer forward; HEAD follows because it points at the branch.

    Being able to say "a branch is a movable label on a commit" answers a surprising number of later questions on its own.

    link
  2. 2

    What is the Git staging area for?

    It is a middle layer between your working directory and a commit, letting you choose what goes into the next snapshot rather than committing everything you happen to have changed.

    AreaHolds
    Working directoryYour files as they are right now
    Staging area, or indexWhat the next commit will contain
    RepositoryCommitted history

    The practical value is committing in coherent pieces. If you fixed a bug and also reformatted a file, git add -p lets you stage the fix alone and commit it separately, which makes review and later bisecting far easier.

    git add -p
    git diff            # working directory against the index
    git diff --staged   # the index against the last commit

    Those two diffs are worth knowing apart, because "why does git diff show nothing" almost always means the change is already staged.

    link
  3. 3

    What is the difference between git fetch and git pull?

    git fetch downloads new commits and updates your remote-tracking branches, such as origin/main. It changes nothing you are working on.

    git pull is fetch followed immediately by a merge, or a rebase with --rebase, into your current branch.

    fetchpull
    DownloadsYesYes
    Changes your branchNoYes
    Can cause a conflictNoYes

    The habit worth stating: fetch, look at what arrived with git log HEAD..origin/main, then decide how to integrate it. pull on a branch with local work is how people end up with a surprise merge commit or a conflict mid-thought.

    git pull --rebase avoids the merge commit, and setting pull.rebase true makes that the default.

    link
  4. 4

    How do you change the last Git commit?

    git commit --amend

    That replaces the last commit with a new one containing the staged changes and an editable message. To fix only the message, use --amend alone; to add a forgotten file, stage it first and use --amend --no-edit.

    The critical caveat, and the reason this is asked: amend does not edit a commit, it creates a different one. The hash changes. If the original was already pushed, your history and the remote's have diverged, and the next push is rejected as non-fast-forward.

    So amend freely on local commits, and treat a pushed commit as published. On a personal branch, --force-with-lease is acceptable; on a shared one, git revert is the answer instead.

    link
  5. 5

    What is the difference between git merge and git rebase?

    Both integrate one branch into another. They differ in what happens to the history.

    Git merge against git rebaseTwo histories. With merge, a feature branch of two commits diverges from main and is joined back by a merge commit, so the branch shape is preserved. With rebase, the same two commits are replayed on top of main as new commits with new hashes, producing one straight line and no merge commit.mergerebaseABXYMmerge committwo parentsfeaturemainABX'Y'Creplayednew hashesmainhistory preserved, graph brancheshistory linear, commits rewritten
    Merge keeps what actually happened and adds a commit with two parents. Rebase produces a straight line, but the replayed commits are new objects with new hashes, which is exactly why rebasing a branch other people have pulled breaks their history.

    Merge creates a new commit with two parents. History is preserved exactly, including the fact that the work happened in parallel, at the cost of a graph with many merge commits.

    Rebase replays your commits one at a time onto the new base, producing new commits with new hashes. History is linear and easy to read, at the cost of no longer being what actually happened.

    MergeRebase
    HistoryPreserved, branchingRewritten, linear
    Commit hashesUnchangedAll new
    Safe on a shared branchYesNo
    ConflictsResolved oncePossibly once per replayed commit

    The rule interviewers listen for: rebase your own unpushed work to tidy it before review, merge when integrating into a shared branch. Never rebase something other people have pulled.

    link
  6. 6

    What is the difference between git reset --soft, --mixed and --hard?

    All three move the branch pointer. They differ in what else they touch.

    Branch pointerStaging areaWorking directory
    --softMovesUntouchedUntouched
    --mixed (default)MovesResetUntouched
    --hardMovesResetOverwritten

    So --soft HEAD~1 undoes the commit and leaves everything staged, which is how you redo a commit message or combine two commits. --mixed additionally unstages, leaving the changes as working edits. --hard throws the changes away.

    --hard is the only one that can lose work, and only uncommitted work: any commit it moves off is still in the reflog for about ninety days.

    git reset --soft HEAD~1    # undo the commit, keep everything staged
    git reset --hard ORIG_HEAD # undo the last dangerous operation entirely
    link
  7. 7

    When would you use git revert rather than git reset?

    When the commit is already shared.

    reset moves the branch pointer backwards, so the commits stop being in the history. On a branch other people have pulled, that is a rewrite: their history and yours no longer agree, and the next person to pull gets a mess.

    revert creates a new commit that applies the inverse change. Nothing is rewritten, the original stays in the history, and everyone's clone stays consistent.

    git revert a1b2c3d          # one commit
    git revert -m 1 a1b2c3d     # a merge commit, keeping the first parent

    That -m flag is worth knowing: reverting a merge is ambiguous because the commit has two parents, so Git needs to be told which line of history to treat as the mainline.

    The rule: reset for private history, revert for public history. On production incidents revert is almost always right, because it is auditable and it does not require anyone else to do anything.

    link
  8. 8

    Your git push is rejected as non-fast-forward. What do you do?

    It means the remote has commits you do not, so your push would discard them. Git is refusing to lose someone's work.

    See exactly how far apart you are:

    git fetch origin
    git rev-list --left-right --count origin/main...HEAD

    The two numbers are commits you are missing and commits you would add. Anything non-zero on the left is why it was refused.

    Then integrate before pushing:

    git pull --rebase origin main
    git push origin main

    The wrong answer, and interviewers are listening for it, is git push --force. That resolves the rejection by deleting the other person's commits. If you genuinely rewrote your own branch, use --force-with-lease, which refuses if the remote moved since your last fetch.

    link
  9. 9

    How do you resolve a Git merge conflict?

    A conflict happens when two branches change the same region of the same file and Git cannot decide. It marks the file and stops.

    git status                    # which files are conflicted
    git diff                      # the conflicting hunks

    The markers show three regions: your side between <<<<<<< and =======, theirs between ======= and >>>>>>>. You edit the file to what it should actually be, which is often neither side verbatim, remove the markers, then stage it.

    git add path/to/file
    git commit                    # or: git rebase --continue
    git merge --abort             # to back out entirely

    Two things that mark experience. git checkout --ours and --theirs take one side wholesale, and during a rebase those two are swapped relative to what you expect, because your commits are being replayed onto theirs. And git rerere, once enabled, remembers how you resolved a conflict and reapplies it, which is genuinely useful on a long-lived branch you rebase repeatedly.

    link
  10. 10

    How do you clean up Git commits before opening a pull request?

    Interactive rebase:

    git rebase -i origin/main

    That opens the list of your commits with a verb against each:

    VerbDoes
    pickKeep as is
    rewordKeep the change, edit the message
    squashMerge into the previous commit, combine messages
    fixupMerge into the previous commit, discard the message
    editStop so you can amend
    dropRemove the commit

    The reason to bother is review and bisect. Six commits reading "wip", "fix", "fix again" tell a reviewer nothing and make git bisect useless later.

    The workflow worth naming is git commit --fixup=<hash> while you work, then git rebase -i --autosquash, which positions the fixups automatically. And because this rewrites hashes, it belongs on your own branch before review rather than after.

    link
  11. 11

    What does git cherry-pick do, and when is it the right tool?

    It applies the change introduced by one commit onto your current branch, as a new commit with a new hash.

    git cherry-pick a1b2c3d
    git cherry-pick a1b2c3d..f4e5d6c   # a range

    Legitimate uses: backporting a hotfix from main to a release branch, or rescuing one commit from a branch that is otherwise being abandoned.

    The caution worth adding is that frequent cherry-picking is usually a smell. It duplicates commits, so the same change exists twice under different hashes, and later merges between those branches can conflict with themselves. If you are cherry-picking regularly, the branching strategy is wrong.

    git cherry -v main release is a useful companion: it lists which commits exist on one branch and not the other, accounting for already-picked changes.

    link
  12. 12

    What does git stash do, and what are its limits?

    It saves your uncommitted changes onto a stack and gives you a clean working directory, so you can switch context without committing half-finished work.

    git stash push -m "wip on auth"
    git stash list
    git stash pop        # apply and remove from the stack
    git stash apply      # apply and keep it

    The limits that catch people out:

    • Untracked files are not stashed by default. You need -u, and ignored files need -a. Losing a new file to this is common.
    • It is a stack, so pop takes the most recent entry, not the one you meant, unless you name it: git stash pop stash@{2}.
    • A dropped stash is hard to find, though git fsck --unreachable can still surface the commit object.

    Worth saying that a throwaway branch with a real commit is often better than a stash for anything you will not resume within the hour, because it has a name and survives being forgotten.

    link
  13. 13

    What is the Git reflog, and how does it save you?

    The reflog records every position HEAD and each branch has pointed at in this clone, with a timestamp. It is local, it is not pushed, and entries live about ninety days by default.

    Because commits are only garbage collected when nothing references them, and the reflog references them, almost anything you "lost" is still there.

    git reflog --date=iso
    git branch recovered a1b2c3d

    That recovers a deleted branch, a bad reset --hard, an interrupted rebase, or a commit you amended away. Branching onto the hash is better than checking it out, which leaves you detached.

    Two extensions worth having. git reflog show origin/main remembers where the remote ref pointed before your last fetch, which is how you recover from somebody else's force push. And git fsck --unreachable --no-reflogs finds dangling objects the reflog has already expired.

    The framing that lands: Git is append-only in practice, and the reflog is why almost nothing is truly destroyed until gc runs.

    link
  14. 14

    What happens when you are in detached HEAD state in Git?

    HEAD normally points at a branch, which points at a commit. Detached means HEAD points straight at a commit, with no branch in between.

    You get there by checking out a hash, a tag, or a remote-tracking ref such as origin/main.

    Everything works, and that is the trap. You can edit and commit normally, and those commits are real. But nothing references them: because no branch moved, checking out another branch leaves them unreferenced and eventually collectable. Git warns you, and the warning is easy to scroll past.

    git switch -c rescue        # name the work before leaving
    git reflog                  # if you already left

    Worth adding that CI systems very often run in detached HEAD, because they check out a specific commit rather than a branch. That explains a category of "the script works locally and not in the pipeline" problems where something depends on the branch name.

    link
  15. 15

    Someone force-pushed over the shared branch. How do you recover?

    Do not panic and do not immediately push, because pushing now risks compounding it.

    If you fetched before the force push, your clone still remembers the old remote tip:

    git reflog show origin/main
    git branch rescue-main origin/main@{1}

    That branch now holds the commits that were overwritten. Compare it to the current remote, work out what was lost, and restore the missing commits with a merge or by cherry-picking them, then push that forward. Restoring by force-pushing the old state back is another rewrite and usually makes it worse.

    If nobody's clone saw the old commits, the remote's own reflog is the only copy and that means asking whoever administers the host.

    The answer interviewers want continues into prevention: protected branches that refuse force pushes, requiring pull requests into main, and making --force-with-lease the team habit so a stale push is refused rather than accepted.

    link
  16. 16

    How does git bisect work, and when would you use it?

    It binary-searches history for the commit that introduced a bug. You mark one commit known bad and one known good, and Git checks out the midpoint repeatedly, halving the range each time. A thousand commits become about ten checkouts.

    git bisect start
    git bisect bad                  # current commit is broken
    git bisect good v1.4.0          # this tag was fine
    # test, then: git bisect good | git bisect bad
    git bisect reset

    The part worth volunteering is automation. If the test is scriptable, Git will do the whole search unattended:

    git bisect run ./scripts/reproduce.sh

    The script exits 0 for good, non-zero for bad, and 125 if the commit cannot be tested.

    This is also the practical argument for clean commit history: bisect is only useful if each commit builds and runs, so a branch squashed into one enormous commit, or a history full of broken intermediate states, defeats it.

    link
  17. 17

    Which Git branching strategy would you choose for a team, and why?

    The answer should start with what the team actually needs, not with a named model.

    Trunk-based development: short-lived branches merged to main within a day or two, with features hidden behind flags. Suits continuous deployment and a team that can keep main releasable. It requires real test automation and feature flags, and without those it becomes chaos.

    GitHub Flow: branch, pull request, merge, deploy from main. A gentler version of the same idea and a sensible default for most web teams.

    Git Flow: long-lived develop, release and hotfix branches. Designed for versioned software with supported releases, such as a shipped product or a mobile app with review delays. On a web service deploying several times a day it adds ceremony without benefit, and its own author has said as much.

    Release branches per version when you genuinely support several versions at once.

    The strongest answer names the real constraint: merge pain grows with branch lifetime, so the question is mostly how long a branch lives, not what the diagram looks like. Then it says what would change the choice, such as a regulated release process or a customer who pins versions.

    link
  18. 18

    What is the difference between a Git submodule and a subtree?

    Both include one repository inside another. They make opposite trade-offs.

    SubmoduleSubtree
    StoresA pointer to a commit in another repoThe actual files, merged in
    CloneNeeds --recurse-submodulesJust works
    Consumers need to knowYesNo
    Updatinggit submodule updategit subtree pull
    HistoryKept separateMerged into yours
    Repo sizeSmallLarger

    Submodules are precise and unforgiving: the parent records an exact commit, which is excellent for reproducibility and a constant source of "I cloned it and the directory is empty" and detached HEAD confusion inside the submodule.

    Subtrees are invisible to consumers, which is their whole advantage, at the cost of a bulkier history and awkward contribution back upstream.

    The answer worth giving is that in most cases neither is right, and a package registry is. Reach for these when you genuinely need source-level inclusion of code you also develop.

    link
  19. 19

    What is the difference between git push --force and --force-with-lease?

    --force overwrites the remote branch unconditionally. If a colleague pushed since your last fetch, their commits are gone and Git will not warn you.

    --force-with-lease first checks that the remote is still where your remote-tracking ref says it is. If somebody pushed in the meantime, it refuses.

    git push --force-with-lease origin feature/user-search

    So it is not "force but safe" in general; it is "force, unless the thing I am about to overwrite has changed since I last looked".

    One caveat worth knowing, because it turns the safety off: running git fetch immediately before updates your remote-tracking ref, so the lease now matches and the check passes. Fetch then force-with-lease is functionally plain force.

    And neither belongs on a shared branch. The real protection is branch protection rules on the host refusing force pushes to main at all.

    link
  20. 20

    What is a .gitignore file, and why does it not work on already-tracked files?

    .gitignore lists paths Git should not start tracking. It has no effect on files already in the index, which is the part that confuses people: adding .env to .gitignore after committing it changes nothing.

    To stop tracking without deleting it locally:

    git rm --cached .env
    git commit -m "Stop tracking .env"

    The critical follow-up for a secret: it is still in the history, and therefore in every clone. Removing it from the current commit does not remove it from the repository, so the credential must be rotated. Purging it properly needs git filter-repo or BFG plus a coordinated force push, and rotating is faster and more reliable.

    A sensible file excludes dependencies, build output, local environment files and editor directories, and git check-ignore -v <path> tells you which rule is matching when something is ignored unexpectedly.

    link
  21. 21

    How do you find which Git commit introduced a specific line?

    git blame -L 40,60 path/to/file

    That annotates each line with the commit, author and date. -L limits it to a range, which matters on a large file.

    The problem with blame alone is that a reformat or a file move puts everyone's name on a line they never wrote. Two flags fix that:

    git blame -w -C -M path/to/file

    -w ignores whitespace changes, -M detects lines moved within the file, and -C detects lines copied from other files.

    For the change rather than the line, git log -S searches for commits where an occurrence count of a string changed, which finds where something was introduced or removed even if the line has since moved:

    git log -S 'DATABASE_URL' --oneline
    git log -L 40,60:path/to/file
    link
  22. 22

    What are the four Git object types, and how do they fit together?

    Everything in .git/objects is one of four, addressed by the SHA of its contents.

    ObjectHolds
    blobFile contents, with no name and no metadata
    treeA directory listing: names, modes, and the blob or tree each points at
    commitA root tree, parent commits, author, committer, message
    tagAn annotated tag: a target object, tagger and message

    A commit points at one tree, which points at blobs and further trees. Because objects are content-addressed, two identical files anywhere in history are one blob, and an unchanged directory between commits is the same tree object reused.

    That is why a commit is a full snapshot yet costs almost nothing: the snapshot mostly re-points at objects that already exist.

    git cat-file -p HEAD
    git cat-file -t a1b2c3d

    Refs, including branches and tags, are just files under .git/refs containing a hash, which is what makes a branch cheap and a checkout fast.

    link
  23. 23

    What are Git hooks, and what would you use them for?

    Scripts Git runs at points in its lifecycle, kept in .git/hooks.

    Client-side: pre-commit runs before a commit is created, commit-msg can validate the message, pre-push runs before a push. Server-side: pre-receive and update can reject a push outright.

    The limitation that matters: `.git/hooks` is not cloned, so a hook you add locally does not reach the team. That is what tools such as pre-commit and Husky exist to solve, by committing the configuration and installing the hooks on setup.

    git config core.hooksPath .githooks

    Setting a tracked hooks directory is the dependency-free version of the same idea.

    The judgement worth adding: client-side hooks are a convenience, not a control, because anyone can skip them with --no-verify. Anything that genuinely must hold belongs in CI or in a server-side rule, and treating a pre-commit hook as a security boundary is a mistake.

    link
  24. 24

    How do you undo changes to a file in Git before committing?

    It depends which stage the change is at, which is the point of the question.

    git restore path/to/file            # discard working directory changes
    git restore --staged path/to/file   # unstage, keep the edit
    git restore --source=HEAD~2 file    # restore it as it was two commits ago

    git restore and git switch were introduced to split what git checkout used to do, because checkout overloaded branch switching and file restoration into one command and that ambiguity caused real accidents.

    The older forms still work and still appear everywhere:

    git checkout -- path/to/file        # same as git restore
    git reset HEAD path/to/file         # same as git restore --staged

    Worth saying that discarding working directory changes is one of the few genuinely irreversible Git operations, because those edits were never committed and so never became an object.

    link
  25. 25

    What is the difference between a Git tag and a branch, and what is an annotated tag?

    Both are refs pointing at a commit. A branch is expected to move as you commit; a tag is not, which is what makes it suitable for marking a release.

    Two kinds of tag:

    LightweightAnnotated
    Stored asA ref pointing straight at the commitA full tag object
    CarriesNothingTagger, date, message, and can be signed
    Use forA private bookmarkReleases
    git tag -a v1.4.0 -m "Release 1.4.0"
    git push origin v1.4.0

    Two things people miss. Tags are not pushed by git push; they need to be pushed explicitly or with --follow-tags. And a tag is not immutable by policy, only by convention: it can be moved and force-pushed, which is exactly why build systems that pin to a tag rather than a commit hash can be surprised.

    link
  26. 26

    How would you remove a committed secret from a Git repository?

    Rotate it first. Everything else is secondary, because the moment it was pushed you must assume it is compromised, and a rewrite cannot un-clone what people already have.

    Then, in order:

    1. 1Rotate the credential. Immediately, before touching history.
    2. 2Rewrite the history with git filter-repo, which is the supported tool now that filter-branch is discouraged, or BFG for the common cases.
    3. 3Force-push every affected branch and tag, and have everyone re-clone rather than merge, because a stale clone reintroduces the objects.
    4. 4Ask the host to garbage collect. On GitHub the old objects remain reachable by hash until support clears cached views, and forks are a separate problem entirely.
    5. 5Add prevention: secret scanning in CI, a pre-commit hook, and push protection on the host.

    The answer that marks experience is leading with rotation and being honest that the rewrite is cleanup, not remediation. A candidate who starts with filter-repo and never mentions rotating the key has answered the wrong question.

    link

Live exercise: you have lost work in Git

Asked in almost every Git round in one form or another. The answer being scored is that you stay calm and know Git rarely deletes anything until garbage collection runs.

  1. Are the changes committed, or only in the working directory?

    git status
    yes
    Committed work is recoverable. Every commit HEAD has pointed at is in the reflog for about ninety days, even on a deleted branch.
    no
    Uncommitted changes that were never staged are genuinely gone; Git never saw them. If they were staged at some point, git fsck --lost-found can still surface the blob.
  2. Can you find the commit in the reflog?

    git reflog --date=iso
    yes
    Note the hash. git branch recovered <hash> puts a name back on it, which is safer than checking it out and ending up detached.
    no
    Widen it. git reflog show --all covers every ref, and git fsck --unreachable --no-reflogs finds dangling commits the reflog has already dropped.
  3. Did somebody force-push over the branch on the remote?

    git reflog show origin/main
    yes
    Your local remote-tracking ref still remembers the old tip from before the fetch. Branch from it, then talk to whoever pushed before you push anything back.
    no
    If your clone never saw the old commits, the remote's own reflog is the only copy, and that needs whoever administers the host.
  4. Was it a bad merge or reset you want to undo rather than recover?

    git reset --hard ORIG_HEAD
    yes
    Git writes ORIG_HEAD before any operation that moves HEAD dangerously, so this undoes the last merge, rebase or reset in one step.
    no
    If the history is already shared, do not rewrite it. Use git revert so the undo is itself a commit and nobody else has to recover.

What each level is testing

  1. 1

    Junior6 questions

    Whether the model is real. What a commit and a branch actually are, the staging area, the difference between fetch and pull, and how to read git status and git log. Being able to say a branch is just a moving pointer is most of the junior answer.

  2. 2

    Mid12 questions

    Whether you have worked on a team. Merge against rebase and when each is right, reset against revert, resolving conflicts, cleaning up history before review, and what --force-with-lease protects you from that --force does not.

  3. 3

    Senior8 questions

    Whether you have recovered from a mess. The reflog, detached HEAD, bisect, what happens to a shared branch after a rebase, submodules against subtrees, and how you would set a branching strategy for a team rather than pick your favourite.

What the round is like

Git questions look easy and are not. Almost everyone can describe add, commit and push, so interviewers move quickly to the parts where people are actually vague: what a branch is, what rebase really does, and what to do when somebody has force-pushed over an afternoon of work. The scenario questions are the round, and they are testing whether you understand the object model rather than whether you have memorised commands.

How to prepare with this

  1. 1Be able to say what a commit object actually contains. Almost every confusing Git behaviour follows from commits being immutable snapshots with parent pointers, and branches being nothing but a movable label.
  2. 2Practise the recovery flow below. "I deleted the branch" and "somebody force-pushed" are asked constantly, and a calm reflog answer is worth more than any amount of command recitation.
  3. 3Learn reset properly: soft, mixed and hard, and which of the three moves the working directory. This is the most commonly fumbled Git answer there is.
  4. 4Never say you would use git push --force on a shared branch. If you mean force, say --force-with-lease and explain what it checks.
  5. 5Have an opinion on trunk-based development against Git Flow, and be able to say which team size and release cadence each suits. Interviewers ask to see whether you reason about the team rather than the tool.

Learn the underlying material

Other question sets