Experienced Git Interview Questions

What is a git submodule?

A Git submodule is a Git repository placed inside another Git repository. The main project keeps only a link to a specific commit of the submodule, and both have their own separate history.

It is useful when a project uses a shared library or a component which has its own repository and its own team. The links are saved in the .gitmodules file.

git submodule add https://github.com/user/library.git libs/library
git clone --recurse-submodules <url>
git submodule update --init --recursive

The common problem is that people forget to initialize or update the submodule after a clone. See Git Submodule.

What is the difference between git merge and git rebase?

git mergegit rebase
Joins two branches, usually with a merge commitMoves your commits on top of another branch
Keeps the real history as it happenedRewrites commits (new hashes) for a straight line
Safe on shared branchesShould not be used on pushed, shared commits
Conflicts are solved onceConflicts may appear for each replayed commit

A common rule: rebase your own local feature branch to update it, and merge when the branch goes back to main. See Git Merge and Git Rebase.

What is the difference between git reset and git revert?

git resetgit revert
Moves the branch pointer back, removing commits from the historyCreates a new commit which cancels the old commit
Rewrites historyHistory is kept
Use for local commits not pushed yetUse for commits already pushed and shared
Has modes: soft, mixed, hardCan undo any older commit, even in the middle

See Git Reset and Git Revert.

What is the difference between soft, mixed and hard reset in Git?

ModeStaging areaWorking files
--softKeeps the changes stagedNot changed
--mixed (default)Cleared, changes unstagedNot changed
--hardClearedChanges deleted
Terminal output

$ git reset –soft HEAD~1
$ git status -s
M f.txt
$ git commit -q -C ORIG_HEAD
$ git reset HEAD~1
Unstaged changes after reset:
M f.txt
$ git status -s
M f.txt
$ git commit -q -a -C ORIG_HEAD
$ git reset –hard HEAD~1
HEAD is now at 2875ee0 First
$ git status -s
$ cat f.txt
one

Look at the M (staged) after soft, M (not staged) after mixed, and the clean status with the line “two” gone after hard. See Git Reset.

What is git cherry-pick?

git cherry-pick takes a single commit from anywhere in the repository and applies it on top of your current branch as a new commit (with a new hash).

git switch main
git cherry-pick a3f9c1e

Typical use: take one bug fix from the develop branch to a release branch, without taking the other unfinished work. Use -x to add the original hash to the message. See Git Cherry Pick.

What is the difference between a lightweight tag and an annotated tag in Git?

Lightweight tagAnnotated tag
Only a name pointing to a commitA full object with tagger, date and message
git tag v1.0git tag -a v1.0 -m "Version 1.0"
No extra informationgit show v1.0 shows who created it, when and why
Good for private or temporary marksRecommended for public releases

See Git Tag.

What is a detached HEAD in Git?

Normally HEAD points to a branch. When you check out a commit, a tag or a remote branch directly, HEAD points to that commit. This is the detached HEAD state.

Terminal output

$ git switch –detach HEAD~1
HEAD is now at 0057196 First
$ git status
HEAD detached at 0057196
nothing to commit, working tree clean

You can look around, but new commits there belong to no branch, and Git may delete them later. To keep them, create a branch: git switch -c new-branch. To leave, use git switch main.

What is a fast-forward merge in Git?

If the target branch has no new commits since your branch was created, Git does not need a merge commit. It just moves the pointer of the target branch forward to the last commit of your branch.

Terminal output

$ git merge feature
Updating 0057196..22ea2c5
Fast-forward
g.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 g.txt

To force a merge commit even in this case, use git merge --no-ff. See Git Merge.

How do you squash commits in Git?

Squashing means joining several small commits into a single clean commit. There are three common ways.

1. Interactive rebase (change “pick” to “squash” for the commits to join):

git rebase -i HEAD~3

2. Reset soft: go back and make one new commit.

Terminal output

$ git reset –soft HEAD~3
$ git commit -q -m "Add feature"
$ git log –oneline
e56c328 (HEAD -> main) Add feature
310ae0b Base

3. Squash merge: git merge --squash feature stages all the changes of a branch, and then you commit once.

Do not squash commits which are already pushed and shared.

What is git reflog and how can it help?

The reflog records every move of HEAD and branch tips in your local repository. Even if a commit is not in any branch anymore, you can find it there.

Terminal output

$ git reflog
0057196 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~1
f411fbd HEAD@{1}: commit: Second
0057196 (HEAD -> main) HEAD@{2}: commit (initial): First
$ git reset –hard HEAD@{1}
HEAD is now at f411fbd Second
$ git log –oneline
f411fbd (HEAD -> main) Second
0057196 First

The reflog is local only, and entries expire after some time (90 days by default). See Git Reflog.

How do you undo a pushed commit in Git?

It depends on who may have already taken your commit.

  • Shared branch (like main): use git revert <commit> and then git push. History is not rewritten, so it is safe for everyone.
  • Your own branch, nobody else uses it: you can use git reset --hard HEAD~1 and then git push --force-with-lease.
git revert HEAD
git push

# only on your own private branch
git reset --hard HEAD~1
git push --force-with-lease

See Git Revert and Git Push.

What is the difference between force push and force with lease in Git?

Both replace the remote branch with your local branch. The difference is the safety check.

–force–force-with-lease
Overwrites the remote branch in any caseOverwrites only if the remote branch is what you last fetched
Can delete commits pushed by your teammatesFails if someone else pushed new commits meanwhile
RiskyMuch safer

Use force push only on your own feature branches after you rewrote the history (rebase or amend), and prefer --force-with-lease. Never force push to main. See Git Push.

What are Git hooks?

Git hooks are scripts in the folder .git/hooks which Git runs before or after events such as commit, merge or push. If a “pre” hook exits with an error, Git stops the action.

HookRunsUse
pre-commitBefore a commitLint or test the code
commit-msgAfter the message is writtenCheck the message format
pre-pushBefore a pushRun the full tests

Hooks are not shared by git push. Teams keep them in a folder and use git config core.hooksPath .githooks, or tools like Husky. See Git Hooks.

What is Git Flow?

Git Flow is a branching model that uses fixed types of branches:

  • main: only released code, tagged with versions.
  • develop: the next release is prepared here.
  • feature/*: new features, created from and merged back to develop.
  • release/*: final fixes before a release.
  • hotfix/*: urgent fixes created from main.

It is good for software with planned versions, but it is heavy for teams which deploy many times a day. Those teams often use a simple feature branch workflow or trunk based development. See Git Branching Strategy.

What is git bisect?

git bisect finds the commit that broke something. You tell Git one “bad” commit (where the bug exists) and one “good” commit (where it worked). Git checks out a commit in the middle, you test it and mark it, and Git halves the range each time.

It can even run a test script for you with git bisect run. In this example, a file starts to contain the word BUG in some commit:

The test script test.sh fails (exit code 1) when the file has BUG:

#!/bin/sh
if grep -q BUG status.txt; then exit 1; fi
exit 0
Terminal output

$ git bisect start
status: waiting for both good and bad commits
$ git bisect bad HEAD
status: waiting for good commit(s), bad commit known
$ git bisect good HEAD~7
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[c9f3864712df8e8c2a36c5713f79b4886088788e] Commit 4
$ git bisect run ./test.sh
running './test.sh'
Bisecting: 1 revision left to test after this (roughly 1 step)
[ec5695fc6c5e58f2148f2bee260b170d5bd4c2bf] Commit 6
running './test.sh'
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[ff72f6258a3d508a43c3e2b650038aaf9b6797aa] Commit 5
running './test.sh'
ff72f6258a3d508a43c3e2b650038aaf9b6797aa is the first bad commit
commit ff72f6258a3d508a43c3e2b650038aaf9b6797aa (HEAD)
Author: Robin Sharma <robin@example.com>
Date: Sat Jan 10 11:03:00 2026 +0000

Commit 5

status.txt | 1 +
1 file changed, 1 insertion(+)
bisect found first bad commit
$ git bisect reset
Previous HEAD position was ff72f62 Commit 5
Switched to branch 'main'

Git found “Commit 5” as the first bad commit. git bisect reset brings you back to the original branch.