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 merge | git rebase |
|---|---|
| Joins two branches, usually with a merge commit | Moves your commits on top of another branch |
| Keeps the real history as it happened | Rewrites commits (new hashes) for a straight line |
| Safe on shared branches | Should not be used on pushed, shared commits |
| Conflicts are solved once | Conflicts 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 reset | git revert |
|---|---|
| Moves the branch pointer back, removing commits from the history | Creates a new commit which cancels the old commit |
| Rewrites history | History is kept |
| Use for local commits not pushed yet | Use for commits already pushed and shared |
| Has modes: soft, mixed, hard | Can 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?
| Mode | Staging area | Working files |
|---|---|---|
--soft | Keeps the changes staged | Not changed |
--mixed (default) | Cleared, changes unstaged | Not changed |
--hard | Cleared | Changes deleted |
$ 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 tag | Annotated tag |
|---|---|
| Only a name pointing to a commit | A full object with tagger, date and message |
git tag v1.0 | git tag -a v1.0 -m "Version 1.0" |
| No extra information | git show v1.0 shows who created it, when and why |
| Good for private or temporary marks | Recommended 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.
$ 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.
$ 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.
$ 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.
$ 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 thengit 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~1and thengit 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 case | Overwrites only if the remote branch is what you last fetched |
| Can delete commits pushed by your teammates | Fails if someone else pushed new commits meanwhile |
| Risky | Much 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.
| Hook | Runs | Use |
|---|---|---|
pre-commit | Before a commit | Lint or test the code |
commit-msg | After the message is written | Check the message format |
pre-push | Before a push | Run 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 todevelop.release/*: final fixes before a release.hotfix/*: urgent fixes created frommain.
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
$ 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.