GitGroundworkGit
17 / 18 written chapters

G17 · Part 4 · Mastery

Interview bank

Questions interviewers actually ask, from fresher to senior, with short answers you can defend under follow-up.

19 min read3 sections
03On this page
  1. Fresher
  2. Mid-level
  3. Senior
Fresher Mid Senior

Each answer is written to be said out loud in under a minute. Scenario questions are marked. In those, the interviewer cares less about the exact command than about the order you do things in and the risk you notice.

Fresher

What's the difference between Git and GitHub?

Git is the version control tool that runs on your machine. GitHub is a hosting service for Git repositories that adds collaboration on top: pull requests, issues, CI, permissions. GitLab and Bitbucket are alternatives. You can use Git with no host at all.

What is a commit?

A snapshot of the whole project at one moment, not a diff. It stores a pointer to a tree (the files), pointers to its parent commits, the author, the committer, and a message. Its ID is a hash of all of that, so changing anything about a commit, even its parent, gives it a new ID.

What does the staging area do?

It's a draft of the next commit. It lets you choose which of your current changes go into this commit and which wait for the next one, so commits stay focused even when you edited several things at once. git add -p goes further and stages part of a file.

What is a branch, and why are branches cheap in Git?

A branch is a file containing one commit hash: a movable label. Creating one writes a few dozen bytes and copies nothing. When you commit, the current branch moves forward to the new commit. That's why Git encourages a branch per task.

What is HEAD?

A pointer to where you are. Normally it points to a branch, which points to a commit. If it points straight at a commit instead, you're in detached HEAD state.

Explain the difference between fetch and pull.

Fetch downloads new commits and updates remote-tracking branches like origin/main. It never touches your files, so it's always safe. Pull is fetch followed by a merge or rebase into your current branch, so it can produce conflicts.

What's the difference between main and origin/main?

main is your local branch. origin/main is your repository's memory of where main was on the remote the last time you fetched. It only moves when you fetch, pull or push, and you never commit to it directly.

What does git clone actually do?

It creates a repository, downloads every commit and object from the remote, adds the remote under the name origin, creates remote-tracking branches for its branches, and checks out the default branch. You get the full history, not just the latest files.

How do you resolve a merge conflict?

git status lists the conflicted files. Open each one and decide what the final code should be, often a mix of both sides. Delete the conflict markers, git add the file to mark it resolved, then git commit (or git rebase --continue during a rebase). git merge --abort backs out completely.

You added a file to .gitignore but Git still tracks it. Why?

.gitignore only affects untracked files. Once a file is tracked, Git keeps tracking it. Run git rm --cached path to stop tracking it while keeping it on disk, then commit. If it held a secret, removing it now does nothing for history: rotate the secret.

When would you use git stash?

When you need a clean working tree for a moment, for example to switch branches for an urgent fix, but your current work isn't ready to commit. git stash sets it aside, git stash pop brings it back. Use -u to include untracked files. For anything longer than an hour, a commit on a branch is safer.

What's the difference between git diff and git diff --staged?

Plain git diff compares your files with the staging area, so it shows what you have not staged yet. git diff --staged compares the staging area with the last commit, which is exactly what the next commit will contain. After git add ., plain git diff prints nothing. git diff HEAD shows both together.

Why do newer guides use git switch and git restore instead of git checkout?

checkout does two unrelated jobs: changing branch and overwriting files, and a typo can turn one into the other. Since Git 2.23, switch only changes branches and refuses to detach HEAD unless asked, and restore only restores files. checkout still works and the Git project has said it will stay, so you need to read both.

What is detached HEAD, and how do you avoid losing work in it?

It happens when you check out a commit or tag directly. You can look around and even commit, but no branch moves with you, so switching away leaves those commits unreachable. If you made commits you want, run git switch -c new-branch before leaving.

Scenario: you made three commits on main that should have been on a feature branch. Nothing is pushed.

git branch feature labels the current commit, so the work is safe. Then git reset --hard origin/main moves main back to where the remote is. git switch feature and carry on. The commits never moved. Only the labels did.

Scenario: there's a typo in your last commit message.

git commit --amend opens the message for editing and replaces the commit. If it's not pushed, that's the end of it. If it's pushed to your own branch, git push --force-with-lease. If it's on a shared branch, leave it. A typo isn't worth rewriting shared history.

Mid-level

Merge or rebase: which and why?

Merge keeps the true shape of history and never rewrites anything, so it's correct for shared branches. Rebase gives a clean linear history but creates new commits, so it's only for work nobody else has. A common policy: rebase your feature branch onto main while you work, then merge or squash-merge the pull request.

Explain the three modes of git reset.

All three move the current branch to another commit. --soft stops there, so your changes stay staged. --mixed, the default, also resets the staging area, so changes stay in your files but unstaged. --hard also overwrites the working tree, which throws away uncommitted work permanently.

When would you use revert instead of reset?

Whenever the commit is already pushed to a shared branch. Revert adds a new commit that undoes the old one, so nobody's history breaks and the record honestly shows the change went in and came out. Reset rewrites history and forces everyone else to recover.

What's a fast-forward merge, and why would you use --no-ff?

If your branch hasn't moved since the feature branched off, Git can just slide the branch label forward, with no merge commit. --no-ff always makes a merge commit, which records that these commits arrived together as one feature and lets you revert the whole feature with one git revert -m 1.

What does interactive rebase let you do?

Rewrite a branch before review: squash noisy commits, reword messages, drop commits, reorder them, or stop at one to change its code. Combined with git commit --fixup and --autosquash, fixes land in the right commit automatically. The goal is commits that each make one coherent, revertable change.

Cherry-pick: what is it and what's the catch?

It applies one commit's changes onto another branch, typically to backport a fix to a release branch. The catch is that it creates a new commit with a new hash, so the same change exists twice in the graph. Use -x so the message records the original hash, which makes the duplicate traceable.

--force versus --force-with-lease?

--force overwrites the remote branch no matter what is there. --force-with-lease only overwrites it if it's still where your origin/... ref says, so it fails instead of deleting a teammate's new commits. Add --force-if-includes to also guard against a background fetch having moved that ref without you noticing.

What is the reflog, and how long does it keep things?

A local log of every position HEAD and each branch has had: commits, resets, rebases, checkouts. It's how you find commits that no branch points to any more. By default entries are kept 90 days, or 30 days for commits that are no longer reachable. It's per clone and never pushed.

What does git pull --rebase do, and why do teams set it as default?

It fetches, then replays your local unpushed commits on top of the new remote tip instead of creating a merge commit. That avoids the "Merge branch 'main' of github.com…" noise in history. Set it with git config --global pull.rebase true. It's safe because it only rewrites commits you haven't pushed.

Squash merging: pros and cons?

Pro: one clean commit per pull request on main, easy to read and revert. Con: the branch's individual commits are lost from main, so bisect can only narrow things down to the whole PR. Also, Git no longer sees the branch as merged, so git branch -d refuses and continuing work on that branch causes repeat conflicts. Start a fresh branch after a squash merge.

What do A..B and A...B mean?

In git log, A..B is the commits reachable from B but not from A, "what B has that A lacks", and A...B is the commits on either side but not both. In git diff the meanings shift: A..B is just the two tips compared, while A...B compares B with the merge base, which is what a pull request shows. Mixing them up is how people get a diff that seems to delete main's newer work.

What does rerere do, and when does it help?

"Reuse recorded resolution". With rerere.enabled on, Git records each conflict and how you resolved it, and replays the resolution when the identical conflict appears again. It pays off when you rebase a long-lived branch repeatedly, or test-merge a branch, abort, and merge again later. Git still stops so you can check the result, and git rerere forget <path> drops a bad recording.

You have three stacked branches, each with an open pull request. The bottom one changes after review. How do you update the others?

Check out the top branch and run git rebase -i --update-refs main (Git 2.38 and later). The to-do list contains update-ref lines for the lower branches, so one rebase moves all three. Then push each with --force-with-lease. If the bottom PR was squash-merged, replay only the commits above it with git rebase --update-refs --onto origin/main <old bottom branch>.

Lightweight versus annotated tags?

A lightweight tag is just a ref pointing at a commit. An annotated tag is a real object with a tagger, date, message and optional signature. git describe and git push --follow-tags use annotated tags by default. Releases should always be annotated.

Scenario: your pull request has fifteen commits like "wip" and "fix lint". The reviewer asks you to clean it up.

git fetch, then git rebase -i origin/main. Squash the noise into a few meaningful commits and reword the messages. Run the tests at the end, then git push --force-with-lease. It's your branch, so rewriting is fine. Mention that review comments tied to old commits may show as outdated.

Scenario: a fix merged to main must also ship in the release/2.3 patch.

git switch release/2.3, git cherry-pick -x <hash>, resolve any conflicts, run tests, push, and tag v2.3.1. For a fix spread across several commits, cherry-pick the range, or cherry-pick the merge commit with -m 1. Fixing on main first and backporting keeps main from missing a fix.

Scenario: you ran git reset --hard HEAD~3 on the wrong branch.

git reflog shows the entry just before the reset, something like HEAD@{1}. git reset --hard HEAD@{1} puts the branch back. The three commits were never deleted, only unreferenced. Any uncommitted changes in files at the time, though, are gone.

Senior

How does Git store data internally?

As a content-addressed object database with four object types: blobs (file contents), trees (directories), commits (a tree, parents and metadata) and annotated tags. Each object's ID is a hash of its content, so identical content is stored once and changing any commit changes the IDs of every commit after it. Objects start as loose zlib-compressed files and are later packed into packfiles with delta compression.

Does Git track renames?

No. A commit only has a tree, and there's no rename record. Commands detect renames on the fly by comparing removed and added files by similarity, 50% by default. That's why a rename plus heavy edits in one commit can show as a delete and an add. Keep big renames in their own commit, and use git log --follow for a single file's history.

What merge strategies does Git have?

ort is the default for two-head merges since Git 2.34, replacing recursive, and since Git 2.50 recursive is only an alias for ort. It's much faster and handles renames better. octopus merges more than two branches if there are no conflicts. The ours strategy records a merge but keeps your tree entirely, which is different from -X ours: that option only picks your side for conflicting hunks and still merges everything else.

What actually happens when you run git commit?

The index already holds a blob for every tracked file, because git add wrote them. Commit turns the index into tree objects, one per directory (git write-tree), writes a commit object naming the root tree, the parent (the current HEAD), author, committer and message (git commit-tree), then moves the branch HEAD points at to the new commit and appends to the reflog (git update-ref). Hooks run around it: pre-commit and commit-msg can abort. Nothing touches the network, and unchanged files cost nothing because their blobs and subtrees are reused.

How do you check what someone changed by hand while resolving a merge?

git show --remerge-diff <merge> (Git 2.36 and later). It redoes the merge in memory, conflict markers included, and diffs that against the commit that was recorded, so the output is exactly the human resolution. The default combined diff hides hunks where the result matches one parent, so a resolution that silently dropped one side's change can look clean. git log --remerge-diff --merges audits a whole range.

What is changing in Git 3.0, and does it affect you?

Git 3.0 has no release date yet, but the planned changes are documented. New repositories will default to SHA-256 object IDs, to the reftable ref backend instead of loose files plus packed-refs, and to main as the first branch name. Existing repositories are not converted. What to do now: set init.defaultBranch, stop assuming hashes are 40 characters, read refs through git rev-parse and for-each-ref rather than files under .git/refs, and check that your tools and host support the new formats before you opt in.

What happens during git gc?

It packs loose objects into packfiles with delta compression, packs refs into one file, expires old reflog entries, and prunes unreachable objects older than the grace period, two weeks by default. The grace period protects objects a running command might be about to reference. Modern setups rely on git maintenance to do this in small background steps.

Git uses SHA-1. Is that a security problem?

SHA-1 collisions have been demonstrated, so Git switched to a hardened SHA-1 implementation that detects the known collision attack and refuses such objects. Git has also supported SHA-256 repositories (git init --object-format=sha256) since 2.29, and the project is moving toward making it the default. Host support is still uneven. Trust in history really comes from signatures on commits and tags, plus protected branches.

What does a signed commit prove?

That someone holding a particular private key created that commit object. Not that the code was reviewed, and not that the author field is true when the signer is someone else, for example after a rebase or a host squash-merge. It's useful for supply-chain policies combined with required reviews and branch protection, not instead of them.

Scenario: a teammate force-pushed and wiped a day of commits from main. Recover them.

The old tip is in the force-push output, in anyone's git reflog show origin/main, or in the host's branch activity log. git branch rescue <hash> to pin it, combine it with the new work, check, and push normally. Then fix the cause: branch protection that blocks force-pushes, and a team alias for --force-with-lease --force-if-includes.

Scenario: a secret got committed and pushed. What now?

Rotate it first. It's compromised the moment it left the machine, and that's the only step that actually closes the hole. Check the provider's logs for use. Then purge with git filter-repo from a fresh clone, force-push every branch and tag, have everyone re-clone, and ask the host to clear cached views and pull request refs. Check forks. Afterwards add push protection and a pre-commit scanner.

Scenario: a regression appeared somewhere in the last 400 commits.

git bisect start, git bisect bad, git bisect good <last known good>. Git binary-searches, about nine steps for 400 commits. With a script that exits 0 for good and 1 for bad, git bisect run ./check.sh does it unattended. Exit code 125 means skip, for commits that don't build. Merge-heavy history works fine. Commits that don't build weaken it, which is an argument for atomic commits.

Scenario: a feature merge broke production. You reverted it. Next week the fixed feature is merged again and half of it is missing. Why?

The revert undid the changes, but Git still records the feature's commits as merged. The second merge only brings in commits made after the first merge, which is just the fix. The fix is to revert the revert first, then merge the fixed branch. Or rebuild the feature on new commits before merging.

Scenario: how would you speed up Git in a large monorepo?

Measure first: GIT_TRACE2_PERF=1, git count-objects -vH and git-sizer tell you whether it's history size, file count or big blobs. Then: blobless partial clones and cone-mode sparse-checkout for developers, or just scalar clone. git maintenance start with commit-graph for history operations. fsmonitor and the untracked cache for status. LFS for binaries. In CI, shallow or treeless clones plus caching.

Scenario: CI only wants to test packages changed in a pull request, but git diff against main fails in CI.

The CI checkout is probably shallow, so git merge-base can't find the common ancestor and git diff origin/main...HEAD breaks. Fetch the base branch and enough depth, or use a blobless clone with full history (fetch-depth: 0 plus --filter=blob:none), which is cheap and keeps merge-base working.

Which branching strategy would you pick for this team, and why?

Tie it to release cadence. A web app deployed continuously: trunk-based or GitHub Flow, because long-lived branches only add merge pain when you ship daily. Several supported versions, or mobile and on-premise releases: release branches cut from main with fixes backported by cherry-pick. Then name the cost: trunk-based needs feature flags and strong CI. Release branches need discipline about backports.

Scenario: a long-lived branch has diverged from main for six months. How do you land it?

Don't try one big rebase. Merge main into the branch in steps, maybe a month of main at a time, with rerere on so repeated conflicts resolve themselves. Get the tests green at each step. Then ship it in slices behind a feature flag rather than one enormous merge. Next time, prevent it by merging main in weekly or not letting branches live that long.

Submodules, subtrees or a monorepo?

Submodules pin another repository at an exact commit. Precise, but everyone must remember --recurse-submodules, and changes that span repositories take two commits and two reviews. Subtree copies the other project's files into yours, so clones are simple but syncing back is manual. A monorepo gives atomic cross-project changes and one set of tooling, at the cost of needing sparse-checkout, partial clone and build tools that understand what changed.

Scenario: you own Git standards for 200 repositories. How do you enforce them?

On the server, because client hooks are optional. Organisation-wide rulesets or branch protection: no force-push or deletion of main, required reviews, required status checks, and optionally signed commits and linear history. CODEOWNERS for review routing. Secret scanning with push protection. Lint commit messages or PR titles in CI. Then give developers a shared hook config so they see failures locally before CI does.

How to answer well

Name the concept, then give one concrete situation where you used it. "Rebase rewrites commits so their hashes change" is a textbook answer. "We rebase feature branches so main stays linear, but not after review starts, because reviewers lose track of what changed" tells the interviewer you've actually lived with the trade-off.