Git Interview Questions for Frontend Developers (With Answers)

Git interview questions for frontend developers: rebase vs merge, pull defaults, reset vs revert, reflog, bisect, lockfile conflicts, leaked keys, hooks and blame.
标签
作者
GreatFrontEnd Team
21 分钟阅读
Oct 22, 2026
Git Interview Questions for Frontend Developers (With Answers)

Git interview questions for frontend developers are rarely about memorizing commands. They are about what you do when something has already gone wrong: a bad commit is on the remote, a rebase ate two days of work, a regression appeared somewhere in the last few hundred commits, or an .env file with a live key was pushed to a public repository. This guide works through 13 such questions. Each answer gives the command, what Git actually does, and the trap that separates a confident answer from a correct one, with a link to the Git or vendor documentation it rests on.

Git behavior has changed in ways that make older answers wrong. git pull no longer quietly merges divergent branches, git restore and git switch are no longer experimental, and the npm documentation page that told you how to fix lockfile conflicts only exists for npm 6. The answers below use current documentation, checked against Git 2.56.0, the latest release listed on git-scm.com at the time of writing, and they say which version introduced a behavior where that matters.

Why Git interview questions for frontend developers look different

Git is close to universal among developers. In the Stack Overflow Developer Survey 2022, which added a version control section that year, 93.87% of respondents and 96.65% of professional developers reported using Git. Because nearly everyone uses it, knowing git commit says little. What separates answers is judgment about shared history, recovery, and the parts of Git that touch a frontend codebase specifically: lockfiles, environment files that get inlined into a client bundle, and pre-commit tooling such as Husky and lint-staged. There is no published data on how often Git comes up in frontend interviews, so treat these as the questions worth being ready for, not a prediction of what you will be asked.

Question 1: When do you rebase a feature branch instead of merging main into it, and when must you not rebase?

How to approach it

Both get main's changes into your branch. A merge adds a merge commit and keeps your commits as they were. A rebase replays your commits on top of the new base, which gives a linear history but creates new commits. The rule that decides it is in the Pro Git book's section on the perils of rebasing: "Do not rebase commits that exist outside your repository and that people may have based work on." The reason follows directly: "When you rebase stuff, you're abandoning existing commits and creating new ones that are similar but different."

So rebasing your own unshared feature branch onto main is fine. Rebasing a branch that a teammate has pulled and built on forces them to reconcile duplicate commits.

If you rebase a branch you already pushed, you need a force push, and the flag matters:

git fetch origin
git rebase origin/main
git push --force-with-lease --force-if-includes

The git push documentation says plain --force "can cause the remote repository to lose commits." --force-with-lease refuses the push unless the remote branch is still where your remote-tracking branch says it is. It has a documented weakness: it "interacts very badly with anything that implicitly runs git fetch on the remote" in the background, such as an editor that fetches on a timer, because the fetch silently updates what you are comparing against. --force-if-includes, added in Git 2.30.0, closes that gap by checking that the remote tip has actually been integrated locally. On its own it is a no-op, so it is always paired with --force-with-lease.

Question 2: What is the difference between git fetch and git pull, and how should pull be configured?

How to approach it

git fetch downloads commits and updates remote-tracking branches such as origin/main. It never touches your working branch. The git pull documentation describes pull as running git fetch and then integrating the fetched branch into the current one.

The part many answers get wrong is the default. Older Git merged divergent branches without asking. Git 2.27.0 added a warning when no preference was configured, and since Git 2.33.1 a pull on divergent branches with no configuration stops with:

hint: You have divergent branches and need to specify how to reconcile them.
fatal: Need to specify how to reconcile divergent branches.

The current documentation describes --ff-only as the behavior "when no method for reconciling divergent histories is provided." You choose a strategy with configuration:

git config --global pull.rebase false # merge
git config --global pull.rebase true # rebase
git config --global pull.ff only # fast-forward only, fail otherwise

A senior answer names a choice and the reason. pull.ff only is the conservative option: a pull never creates a merge commit or rewrites anything, and when it fails you decide explicitly. Note that the documentation calls pull.rebase "a possibly dangerous operation", which is the shared-history rule from Question 1 again.

Question 3: A bad commit is already pushed. When do you use git revert, git reset or git restore?

How to approach it

The three commands operate at different levels:

CommandWhat it changesSafe on a pushed, shared branch?
git revert <commit>Adds a new commit that undoes an earlier oneYes, history is only added to
git reset <commit>Moves the branch pointer, and with --hard the filesNo, it rewrites history others may have
git restore <path>Restores files in the working tree or indexNot a history operation at all

For a pushed commit on a shared branch, the answer is git revert. The git revert documentation describes it as recording "some new commits to reverse the effect of some earlier commits." Nobody else's history changes.

git reset "changes which commit HEAD points to", per the git reset documentation. The default is --mixed, which keeps your file changes unstaged. --hard overwrites files and "may overwrite untracked files." Use it on local commits you have not pushed.

git restore is for files, not commits. It was introduced in Git 2.23.0 alongside git switch, and Git 2.51.0's release notes declared both "no longer experimental", so answers that call them experimental are out of date.

Reverting a merge has a trap worth naming. You need -m to say which parent is the mainline, and the documentation warns that reverting a merge "declares that you will never want the tree changes brought in by the merge." If the feature branch is fixed and merged again later, its earlier changes will not come back. You have to revert the revert first.

Question 4: You ran a hard reset or a rebase and lost commits. How do you get them back?

How to approach it

Commits are rarely gone immediately. The reflog records where branch tips and HEAD have been in your local repository, including positions that no branch points to any more:

git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~3
# 9f8e7d6 HEAD@{1}: commit: Add cart total rounding
git branch recovered 9f8e7d6

HEAD@{2} means "where HEAD used to be two moves ago." Commands such as reset, rebase and merge can also record the previous branch tip in ORIG_HEAD, which may help with recovery. However, later operations can overwrite it. Use git reflog to identify the commit you want to recover, and create a branch pointing to it before making further changes. Avoid running git reset --hard unless you also intend to discard your current tracked working-tree changes.

Two limits make this a real answer rather than "the reflog has everything". The reflog is local: it does not exist on the remote or in a teammate's clone. And it expires. Per the git config documentation, gc.reflogExpire defaults to 90 days, but gc.reflogExpireUnreachable defaults to 30 days, and that is the one that applies to commits you abandoned with an amend, reset or rebase.

Question 5: A regression appeared somewhere in the last 200 commits. How do you find the commit that caused it?

How to approach it

Use git bisect, which "uses a binary search algorithm to find which commit in your project's history introduced a bug." You mark one known good commit and one known bad one, and Git checks out the midpoint each time:

git bisect start
git bisect bad # current commit has the bug
git bisect good v4.2.0 # this release did not
# test, then mark each step: git bisect good / git bisect bad
git bisect reset # back to where you started

A binary search over 200 commits needs about 8 steps, since 2 to the power of 8 is 256. The stronger answer automates it. git bisect run executes a script at each step, and the exit code decides the verdict. The documentation says to exit 0 when the code is good, and with "a code between 1 and 127 (inclusive), except 125" when it is bad. The special code 125 means the commit "cannot be tested" and is skipped, which matters in a frontend repository where some intermediate commits do not build:

git bisect run sh -c 'npm ci || exit 125; npx vitest run src/cart.test.ts'

A failed install exits 125 and skips the commit. A failing test exits non-zero and marks it bad. Writing the failing test first, as a small reproduction, is most of the work, and it leaves you with a regression test once the bisect is done.

Question 6: You get a merge conflict in package-lock.json. How do you resolve it?

How to approach it

Do not resolve it by hand, and do not delete the lockfile and regenerate it from scratch, which can silently upgrade every dependency within its semver range. Resolve the package.json conflict first, because that is the file that states intent, then let the package manager rebuild the lockfile from it.

What each tool documents differs:

  • npm. The explicit instruction is in the npm 6 documentation: fix any package.json conflicts, then run npm install and "npm will automatically resolve any conflicts for you and write a merged package lock." Current npm documentation (v7 through v12) no longer has that section. Current npm still parses conflicted lockfiles through its parse-conflict-json dependency, whose README describes resolving JSON with Git merge conflicts "if possible". So the workflow still works, but cite it carefully.
  • pnpm. The pnpm Git page says pnpm "can automatically resolve merge conflicts in pnpm-lock.yaml. If you have conflicts, just run pnpm install and commit the changes." It adds that you should review the result, "because we cannot guarantee that pnpm will choose the correct head."
  • Yarn 2 and later. The error code reference describes merging the lockfile automatically on install, and notes that under yarn install --immutable, the usual CI mode, Yarn may not edit files, not even to resolve conflicts.

After regenerating, read the lockfile diff before committing. The point is to confirm that only the packages from the two branches changed. Why the lockfile matters at all, and what npm ci does with it, is covered in GreatFrontEnd's npm and package management interview questions.

Question 7: A file you added to .gitignore still shows up in git status. Why, and what do you do?

How to approach it

Because .gitignore only applies to untracked files. The gitignore documentation says so in its first lines: "Files already tracked by Git are not affected." If dist/ or .env.local was committed before the ignore rule existed, Git keeps tracking it.

The same page gives the fix: "use git rm --cached to remove the file from the index."

git rm -r --cached dist
git commit -m "Stop tracking build output"

--cached removes the path from the index only, and the git rm documentation confirms that working tree files "will be left alone." Two follow-ups show you understand the consequences. The file still exists in every earlier commit, so if it was a secret, this does not remove it (see the next question). And teammates who pull this commit will have the file deleted from their working tree, because Git sees the removal as a change, so warn them first if it is a local config file they need.

Question 8: Someone committed an .env file with an API key. What do you do, in order?

How to approach it

Rotate the key first. GitHub's guide to removing sensitive data from a repository says that for a secret, "as a first step you need to revoke and/or rotate that secret," and that rewriting history afterwards "may not be warranted." Once a key has been pushed, assume it has been copied. Rewriting history does not reach clones, forks, or cached views.

Then decide whether to rewrite history. If necessary, follow GitHub's sensitive-data removal procedure using git-filter-repo in a fresh clone. For a file named .env, the core commands are:

git-filter-repo --sensitive-data-removal --invert-paths --path .env
# Re-add origin if git-filter-repo removed it.
git remote add origin <repository-url>
# After reviewing the rewritten history and coordinating with collaborators:
git push --force --mirror origin

Run git remote add only if origin is missing. A mirror push can overwrite remote branches and tags, so verify the rewritten references before pushing.

The same guide lists what this does not fix: the data can stay reachable "in any clones or forks", through cached views of the old commit hashes, and through pull requests that reference them. Removing cached views needs GitHub Support, which only assists when rotation cannot mitigate the risk. Collaborators then have to rebase rather than merge, or an old clone puts the file back.

The frontend-specific half of the answer is that a key can leak without ever being committed. Build tools inline some environment variables into the JavaScript you ship. Vite's environment variables guide says variables prefixed with VITE_ "will be exposed in client-side source code after Vite bundling" and should not contain API keys. Next.js does the same for the NEXT_PUBLIC_ prefix, which its environment variables guide describes as inlining a value "into the js bundle that is delivered to the client." A secret behind either prefix is public no matter what .gitignore says.

Prevention is the last step. GitHub push protection blocks pushes that contain recognized secrets. The user-level setting is on by default for pushes to public repositories, while repository-level push protection is off by default and requires GitHub Secret Protection.

Question 9: How do you run lint and tests before every commit, and why is a pre-commit hook not enough?

How to approach it

The common setup is Husky to install the hook and lint-staged to run tools only on staged files:

npm install --save-dev husky lint-staged
npx husky init

npx husky init creates .husky/pre-commit and sets up the prepare script in package.json, so the hook is installed on npm install. Husky works through Git's core.hooksPath setting, which Git 2.9.0 introduced, and points Git at a hooks directory that is committed to the repository. That matters because Git's default hooks directory is not shared: the Pro Git chapter on Git hooks says "client-side hooks are not copied when you clone a repository."

{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.css": "prettier --write"
}
}

Finally, update .husky/pre-commit to run lint-staged:

npx lint-staged

Now, each commit runs ESLint and Prettier on matching staged files. Full tests and type checking can run in CI.

Then the second half of the question. A hook runs on the developer's machine and can be skipped. The git commit documentation says --no-verify will "bypass the pre-commit and commit-msg hooks," and Husky documents HUSKY=0 to disable its hooks. Pro Git's advice is direct: if the intent is to enforce a policy, "you'll probably want to do that on the server side." So the same lint, type check and tests run in CI as a required status check, and the local hook is a fast feedback loop, not the gate. Keeping the hook fast, by linting only staged files and leaving the full test suite to CI, is what stops people from reaching for --no-verify in the first place. GreatFrontEnd's senior testing interview questions cover what that CI test layer should contain.

Question 10: How do you clean up a branch's commits before review, and which merge strategy should the repository use?

How to approach it

For cleanup, interactive rebase on your own unshared branch. In the editor, squash folds a commit into the one before it and combines the messages, while fixup folds it in and discards its message. You can prepare this while working with git commit --fixup=<commit>, which creates a commit titled fixup! ..., and then:

git rebase -i --autosquash origin/main

--autosquash moves each fixup! commit next to its target and marks it for you, per the git rebase documentation. Setting rebase.autoSquash makes that the default.

The merge strategy is a team decision with real trade-offs, and GitHub's page on merge methods states them:

MethodResulting historyWhat you give up
Merge commit (default)Every branch commit plus a merge commitA linear log
Squash and mergeOne commit per pull requestPer-commit history and authorship detail inside the pull request
Rebase and mergeEach commit replayed onto the base, no merge commitOriginal commit hashes. GitHub creates new ones, updates committer information, and drops empty commits

A reasoned answer ties the choice to how the history is used. Squash suits small pull requests where one commit per change makes git revert and git bisect simple. A merge commit suits large branches whose individual commits are meaningful. If the team wants to enforce linear history, GitHub's "require linear history" branch protection rule allows only squash or rebase merges, per its protected branches documentation.

Question 11: A fix on main needs to go to the release branch. How do you move just that commit?

How to approach it

git cherry-pick applies the change from one commit as a new commit on the current branch:

git switch release/4.2
git cherry-pick -x 3c9d2e1

The detail that shows experience is -x. The git cherry-pick documentation says it appends "(cherry picked from commit …)" to the message, and recommends it when cherry-picking between publicly visible branches, for example "backporting a fix to a maintenance branch for an older release." Six months later, anyone can trace the release commit back to its source. The same page says not to use it when picking from a private branch, because the reference would point at a commit nobody else can see.

Two follow-ups are worth having ready. The new commit has a different hash, so the same change now exists twice, which is expected for a backport. And if the fix depends on earlier commits that are not on the release branch, the pick may conflict or, worse, apply cleanly and break, so run the release branch's tests before tagging.

Question 12: You are mid-feature and need to fix an urgent bug. Do you use git stash or git worktree?

How to approach it

Either works. They trade simplicity for isolation.

git stash saves your uncommitted changes and cleans the working tree. Untracked files are not included unless you pass -u (--include-untracked), so a new component file you have not added yet stays in the working tree and follows you onto the hotfix branch, per the git stash documentation. If git stash pop hits a conflict, the entry is not removed from the stash list, so nothing is lost.

git worktree gives you a second working directory attached to the same repository, so both branches are checked out at once:

git worktree add ../shop-hotfix -b hotfix/cart-total origin/main
cd ../shop-hotfix && npm ci

Nothing in your feature directory is touched, and a long-running dev server there keeps running. The cost on a frontend project is a second node_modules install, and the git worktree documentation still notes that "multiple checkout in general is still experimental" and that submodule support is incomplete. Git also refuses to check out the same branch in two worktrees unless you force it. A practical answer: stash for a five-minute interruption, worktree when the fix needs its own install, dev server or review time.

Question 13: How do you find who changed a line and why, when the history includes a large formatting commit?

How to approach it

git blame shows the last commit that touched each line, and after a repository-wide Prettier run that commit is the formatter for nearly every line. Git 2.23.0 added the fix. The git blame documentation describes --ignore-rev as ignoring a revision "as if the change never happened", so lines it changed are blamed on the previous commit that touched them.

echo "4f1a2b3c... # Prettier 3 reformat" >> .git-blame-ignore-revs
git config blame.ignoreRevsFile .git-blame-ignore-revs

The file takes full commit hashes, one per line, with # comments. GitHub's blame view reads a .git-blame-ignore-revs file in the repository root automatically, per its documentation on viewing files.

Blame answers who last touched a line, which is often not the question. Two git log options from the git log documentation go further. git log -S'formatPrice(' finds commits that changed the number of occurrences of a string, which is how you find when a function call was added or removed. git log -L :formatPrice:src/utils/price.ts traces the history of one function across every commit that changed it. Reading the commit message and the linked pull request then tells you why.

Common mistakes in Git interview questions for frontend developers

  • Saying git pull merges by default. Since Git 2.33.1, a pull on divergent branches without configuration stops and asks.
  • Using git reset --hard on a pushed branch when git revert was the safe tool.
  • Treating the reflog as permanent or shared. It is local and unreachable entries expire after 30 days by default.
  • Rewriting history before rotating a leaked key, or assuming a force push removes the secret from forks and clones.
  • Putting a secret behind VITE_ or NEXT_PUBLIC_, which ships it to every browser.
  • Relying on pre-commit hooks as enforcement. They are skipped with --no-verify and must be backed by CI.
  • Deleting the lockfile to fix a conflict, which can upgrade unrelated dependencies.

Frequently asked questions

Do I need to memorize Git command flags for frontend interviews? No. Know what each command does to history and to the working tree, and when it is safe on a shared branch. Exact flags can be looked up. The difference between revert and reset cannot be guessed under pressure.

Merge or rebase: which is correct? Neither, in general. Rebase your own unshared work for a clean history. Merge, or rebase with care, when others have built on the branch. Then name the team's merge method and why.

Is git switch the same as git checkout? For changing branches, git switch covers the branch half of git checkout, and git restore covers the file half. Both have been stable since Git 2.51.0.

What should a frontend repository put in .gitignore? At least node_modules, build output such as dist or .next, local environment files, and editor or OS files. Commit the lockfile.

How to prepare

Practise the recovery questions in a throwaway repository rather than reading about them. Make five commits, hard reset three of them away and recover them from the reflog. Introduce a bug in commit 30 of 40 and find it with git bisect run and a one-line test. Create a conflicting package.json change on two branches and resolve the lockfile with npm install. Set up Husky and lint-staged, then commit with --no-verify and watch the hook not run. Each exercise takes minutes and turns an answer you have memorized into one you have seen happen. For the dependency side of frontend tooling, GreatFrontEnd's frontend build tools interview questions pair well with this list, and the guide to a strong frontend developer GitHub profile covers how your public repositories read to a reviewer.

Conclusion

Git interview questions for frontend developers test whether you can protect shared history and recover when something goes wrong. Rebase only what nobody else has built on and force-push with a lease. Configure git pull instead of relying on a default that has changed. Revert on shared branches and reset only locally. Recover lost work from the reflog before it expires, and find regressions with git bisect run. Let the package manager rebuild a conflicted lockfile, and rotate a leaked key before rewriting history. Treat hooks as feedback and CI as enforcement, and know the cost of each merge method. If you can explain the command, what it does to history, and the trap, for each of the 13 questions here, you are answering at the level these questions are looking for.

相关文章

npm and Package Management Interview Questions for Frontend Developersnpm package manager interview questions rarely appear by name. Here is what lockfiles, npm ci, semver ranges and install scripts really do, with worked examples.
Frontend Build Tools Interview Questions: Vite, webpack, and BundlingFrontend build tools interview questions on Vite, webpack, bundling, tree shaking, content hashing, source maps and chunking, with real configs and answers.
Senior Testing Developer Interview Questions: Advanced Topics and AnswersSenior testing developer interview questions on flaky tests, mocking, coverage, and E2E architecture, with worked answers and real, sourced examples throughout.
Frontend Developer GitHub Profile: How to Stand Out to Recruiters (2026)Build a reviewable frontend developer GitHub profile with a focused README, proof-led pinned repositories, project READMEs, demos, and cleanup checks.