Git and GitHub Interview Questions 2026
Almost nobody fails an interview because they didn't know Git, but a surprising number of people get tripped up by questions that go one level deeper than git add, git commit, git push. Interviewers like Git questions specifically because everyone claims to know it, so it's an easy way to separate "I use three commands on autopilot" from "I actually understand what's happening when I run them."
This page covers the usual suspects, merge vs rebase, resetting vs reverting, but also spends real time on the stuff that actually comes up when things go wrong: recovering a lost commit, cleaning up a messy rebase, and what you'd actually do if a secret got committed to a public repo.
Why Git Questions Come Up in Every Interview
Git is one of the few tools that's genuinely universal across teams, languages, and stacks, so it's fair game in almost any technical interview, not just for specific "DevOps" roles. Interviewers use it to check:
- Whether you understand what a commit and a branch actually are, not just the commands
- Whether you can recover from a mistake instead of panicking (lost commits, bad rebases, force-push accidents)
- Whether you've actually collaborated on a shared repo, PRs, conflicts, code review workflow
- Whether you know when a tool like rebase is genuinely useful versus when it's actively dangerous
Who This Page Is For
- Anyone prepping for a technical interview, since Git questions show up regardless of your specific stack
- Developers who use Git daily but have never had to explain what's inside the
.gitfolder - Freshers who've only used Git through a GUI or IDE integration and want to be comfortable with the actual commands
Related Resources
Git and GitHub Interview Questions and Answers (2026)
Git Fundamentals
1. What's the actual difference between Git and GitHub? People use these interchangeably all the time.
Git is the version control system itself, a command-line tool (and the underlying data model) that tracks changes to files over time, and it runs entirely on your machine, no internet connection required. GitHub is a hosting platform built around Git, it stores your repositories remotely and adds a layer of collaboration tools on top, pull requests, issues, code review, Actions for CI/CD. You could use Git your entire career without ever touching GitHub, plenty of teams use GitLab or Bitbucket instead, or even just a private server.
2. What's the difference between the working directory, the staging area, and the repository, the "three trees" of Git?
The working directory is the actual files on your disk that you're editing right now. The staging area (also called the index) is a middle step where you tell Git exactly which changes you want included in the next commit, using git add, it's what lets you commit only part of your changes instead of everything you've touched. The repository is the committed history itself, the actual snapshots that git commit has permanently recorded. A change has to move through all three: edit a file (working directory), stage it (git add), then commit it (git commit) before it's actually part of your project's history.
3. What does git init actually do to a folder?
It creates a hidden .git directory inside your project folder, which is where absolutely everything Git tracks actually lives, commit history, branches, configuration, all of it. Nothing outside .git gets touched or reorganized, your existing files stay exactly where they are, Git just starts watching that folder from that point forward. If you ever delete the .git folder, the folder instantly stops being a Git repository, even though all your actual files are still sitting right there untouched.
4. What's the difference between git clone and running git init and then adding a remote yourself?
git clone does several things in one step: it creates the local directory, initializes it as a Git repo, copies the entire history from the remote, automatically sets that remote as origin, and checks out the default branch, all in one command. Doing it manually with git init plus git remote add origin <url> gets you an empty repo with a remote configured, but you'd still need to git fetch and git checkout to actually pull down the history and files, clone is really just a convenient shortcut for the whole sequence.
5. What is a commit, really, at the level Git actually stores it, not just "a snapshot"?
A commit is an object that points to a full snapshot of your entire project's file tree at that moment, plus metadata: the author, the commit message, a timestamp, and a pointer to its parent commit (or commits, for a merge). It's worth knowing it's a full snapshot, not a diff, Git computes diffs on the fly for display purposes, but what's actually stored is the complete state of every tracked file at that point, which Git makes efficient through compression and reusing unchanged file content between commits.
6. What's the difference between git add . and git add -A, do they actually behave differently?
In modern Git (2.x), they're effectively the same, both stage all changes, new, modified, and deleted files, across the entire repository. Historically, in older Git versions, git add . only staged changes in the current directory and below, and didn't pick up file deletions the same way -A did. If you're on any reasonably recent Git version, this distinction mostly doesn't matter anymore, but it's a fair "do you know Git's history" style question.
Branching and Merging
1. What is a branch in Git, really, at the storage level?
A branch is just a lightweight, movable pointer to a specific commit, that's it. It's not a separate copy of your files sitting somewhere, it's literally a small text file containing a commit hash. When you commit while on a branch, Git creates the new commit and then just moves that pointer forward to the new commit. This is why creating a branch in Git is nearly instant and cheap, unlike some older version control systems where branching meant physically copying the entire codebase.
2. What's the difference between a fast-forward merge and a three-way merge?
A fast-forward merge happens when the branch you're merging into hasn't moved since you branched off it, Git can just slide the branch pointer forward to the tip of the other branch, no actual merge commit is needed since there's nothing to reconcile. A three-way merge happens when both branches have diverged, each has commits the other doesn't, so Git has to look at the common ancestor plus both branch tips, combine the changes, and create a new merge commit that has two parents to record that combination.
3. What causes a merge conflict, and what's actually happening in the file when you see the conflict markers?
A conflict happens when both branches changed the same lines of the same file in different ways (or one branch deleted a file the other modified), and Git genuinely can't tell which version you want, so it stops and asks you to decide. The conflict markers (<<<<<<<, =======, >>>>>>>) show both versions directly in the file, your current branch's version above the ======= and the incoming branch's version below, and resolving it means editing the file to keep whatever combination is actually correct, then removing the markers and staging the file.
4. What's the difference between git merge and git rebase, and when would you actually pick one over the other?
git merge combines two branches by creating a new merge commit that ties both histories together, preserving exactly how things actually happened, including the fact that work happened in parallel. git rebase takes your branch's commits and replays them one by one on top of the target branch, rewriting your commits with new hashes, resulting in a clean, linear history with no merge commit at all. Rebase gives you a tidier history, which a lot of teams prefer for feature branches before merging into main, but merge preserves the true, honest history of what happened, which matters more on shared branches where rewriting history would break things for everyone else who already has those commits.
# merge keeps both histories and adds a merge commit
git checkout main
git merge feature-branch
# rebase replays feature-branch's commits on top of main, linear history
git checkout feature-branch
git rebase main
5. What is a detached HEAD state, and how do you get out of it safely?
Normally, HEAD points to a branch, which in turn points to a commit. A detached HEAD happens when HEAD points directly to a specific commit instead, usually because you checked out a commit hash or a tag directly rather than a branch name. Anything you commit while detached isn't attached to any branch, so if you switch away without saving that work, it can become genuinely hard to find again, orphaned commits eventually get garbage collected. To get out safely, either checkout a real branch if you didn't mean to make changes, or if you did make commits you want to keep, create a new branch right where you are (git checkout -b new-branch-name) before switching away.
6. What's the difference between git branch -d and git branch -D?
-d (lowercase) deletes a branch, but only if Git can confirm it's already been fully merged somewhere, it refuses and warns you otherwise, protecting you from accidentally losing unmerged work. -D (uppercase) is shorthand for --delete --force, it deletes the branch regardless of merge status, no safety check at all. If you're not sure a branch's work is actually captured elsewhere, -d is the safer default, and Git yelling at you when you try to use it is actually useful information, not just an annoyance.
Rebasing, Cherry-Picking and History Rewriting
1. What does an interactive rebase (git rebase -i) actually let you do?
Interactive rebase opens an editable list of commits (in the range you specify) and lets you decide what happens to each one, keep it as-is (pick), edit its content or message (edit/reword), combine it with the previous commit (squash/fixup), reorder commits, or drop them entirely. It's the main tool for cleaning up a messy commit history, turning "wip", "fix typo", "actually fix it this time" into one clean, well-described commit before it becomes part of the permanent shared history.
git rebase -i HEAD~5 # interactively edit the last 5 commits
2. What is squashing, and why would a team require squashed commits before merging a PR?
Squashing combines multiple commits into a single commit, keeping (or rewriting) just one final commit message instead of the full trail of intermediate ones. Teams often require it because a feature branch's history is usually full of noise, work-in-progress commits, review fixes, typo corrections, that don't add value to the project's permanent history, and squashing everything into one clean commit makes git log and git blame on the main branch dramatically easier to actually read and understand later.
3. What is git cherry-pick, and what's a legitimate use case for it?
git cherry-pick <commit-hash> applies the changes from one specific commit onto your current branch, without bringing along the rest of that commit's branch history. A legitimate use case is a hotfix, if a critical bug fix was committed on a development branch but you need it immediately on a production/release branch without merging in everything else that branch has accumulated, cherry-picking just that one fix commit gets it there cleanly.
4. Why is rewriting history (rebase, amend, force push) considered dangerous on a shared branch?
Because rewriting history changes commit hashes, and if anyone else has already pulled the old commits, their local history now has commits that no longer exist on the remote after you force-push the rewritten version. Their next pull can create a confusing mess, duplicate commits, unexpected conflicts, or in the worst case, someone force-pushes their old version back and silently erases your changes. The general rule people repeat for a reason: it's fine to rewrite history on a branch only you are using, never on a branch other people have already pulled from.
5. What's the difference between git rebase and git rebase --onto?
A plain git rebase <base> replays your entire current branch's commits on top of <base>. git rebase --onto <newbase> <oldbase> <branch> is more surgical, it lets you take only the commits between <oldbase> and <branch> and replay just that specific range onto <newbase>, which is useful when you need to move a chunk of commits to a completely different branch without dragging along commits from an intermediate branch they were originally stacked on.
Undoing Changes and Recovery
1. What's the difference between git reset, git revert, and git checkout when it comes to undoing changes?
git reset moves the branch pointer backward (or to a specific commit), effectively undoing commits, and depending on the flag, can also touch your staging area and working directory, this rewrites history, so it's meant for local, unpublished commits. git revert creates a brand new commit that applies the inverse of a previous commit's changes, the original commit still exists in history, which makes it the safe option for undoing something that's already been pushed and shared. git checkout (for undoing) restores specific files to a previous state without touching commit history at all, more of a "discard my current uncommitted edits" tool.
2. What's the difference between --soft, --mixed, and --hard with git reset?
--soft moves the branch pointer but leaves your staging area and working directory untouched, so all the changes from the "undone" commits show up as staged changes, ready to be recommitted differently. --mixed (the default if you don't specify a flag) moves the pointer and unstages those changes, but keeps them in your working directory as unstaged edits. --hard moves the pointer and wipes both the staging area and the working directory, actually discarding those changes entirely, which is the genuinely destructive one if you have uncommitted work you care about.
3. How do you recover a commit you accidentally lost, say after a hard reset?
As long as it wasn't garbage collected yet (which usually gives you a real window, not instant), it's still sitting there. Run git reflog, which shows a log of everywhere HEAD has pointed recently, including commits that are no longer reachable from any branch, find the commit hash you lost, and either git checkout <hash> to look at it or git reset --hard <hash> (or create a new branch at that hash) to get your branch back to that state.
git reflog # find the lost commit's hash
git checkout -b recovered abc1234 # create a branch pointing to it
4. What is git reflog, and why is it basically your safety net?
git reflog is a local log of every place HEAD has pointed to on your machine, every commit, checkout, reset, rebase, over a rolling window (by default around 90 days for reachable entries). It's your safety net because commits aren't actually deleted the instant you "lose" them through a reset or a bad rebase, they just become unreachable from any branch, but Git keeps them around until garbage collection eventually cleans them up. Reflog is genuinely one of the most reassuring tools in Git once you know it exists, "I think I just destroyed my work" is very often recoverable.
5. What's the difference between git stash and just committing work-in-progress changes to a throwaway branch?
git stash temporarily shelves your uncommitted changes (staged and unstaged) and gives you back a clean working directory, without creating a real commit in your history, useful for quickly switching context, say to fix an urgent bug on another branch, without committing half-finished work. Committing to a throwaway branch achieves something similar but creates real commits with real hashes that show up in history and can be pushed, shared, or referenced more permanently. Stash is meant to be quick and temporary, if you need to actually share the work-in-progress with someone else or keep it around long-term, a real branch and commit is usually the better tool.
Remote Repositories and Collaboration
1. What's the difference between git fetch and git pull?
git fetch downloads new commits and branches from the remote into your local repository, but doesn't touch your current working branch at all, it just updates your local view of what the remote looks like. git pull does a fetch and then immediately tries to merge (or rebase, depending on config) those changes into your current branch. Fetch is the safer, non-destructive option when you just want to see what's changed remotely before deciding what to do about it, pull is the "just get me up to date right now" shortcut.
2. What does git push --force actually do, and why is --force-with-lease generally safer?
git push --force overwrites the remote branch's history with your local version, no questions asked, even if someone else has pushed commits to that branch since you last fetched, those commits get silently discarded on the remote. --force-with-lease adds a safety check, it only force-pushes if the remote branch still matches what you last saw when you fetched, if someone else pushed in the meantime, it refuses and tells you to fetch first, preventing you from accidentally clobbering their work without even realizing it happened.
3. What's the difference between origin and upstream when working with a forked repository?
When you fork a repo on GitHub and clone your fork, origin conventionally refers to your fork, the remote you actually have push access to. upstream is a remote you typically add yourself, pointing to the original repository you forked from, which you generally only have read access to but need in order to pull in the latest changes from the original project and keep your fork up to date.
git remote add upstream https://github.com/original-owner/repo.git
git fetch upstream
git merge upstream/main
4. What happens when two people push conflicting changes to the same branch?
Git enforces that pushes must be fast-forwards by default, so whoever pushes second gets rejected with a "non-fast-forward" error, since their local branch doesn't include the commit the other person just pushed. They have to pull (or fetch and merge/rebase) first to incorporate the other person's changes, resolve any actual content conflicts if the same lines were touched, and only then can they push successfully, this is exactly the mechanism that prevents someone from silently overwriting someone else's pushed work by accident.
5. What's a tracking branch, and how does Git decide which remote branch your local branch is tracking?
A tracking branch is a local branch that's associated with a specific remote branch, which is what lets you run a plain git pull or git push without specifying the remote and branch name every time, Git knows where to send or fetch from. This association is usually set automatically when you git checkout a remote branch for the first time, or when you push a new branch with -u (git push -u origin my-branch), which sets up the tracking relationship for future pushes and pulls.
Git Internals
1. What are Git's four core object types, and what does each one store?
Blob stores the raw content of a file, no filename, just the data. Tree represents a directory, listing filenames and pointing to the blobs (or other trees, for subdirectories) inside it. Commit points to a single tree (the state of the whole project at that point) plus metadata, author, message, parent commit(s). Tag is a named, permanent pointer to a specific commit, typically used for marking releases.
2. What's the difference between a tree object and a blob object?
A blob is just file content, pure data with no name or structure attached to it at all. A tree is what gives that content structure, it's essentially a directory listing that maps filenames to blob (or nested tree) hashes, this separation is actually clever, it means if the exact same file content exists in two different places in your project, Git only has to store that blob once and both trees just reference the same hash.
3. How does Git generate a commit hash, and why does changing anything about a commit, even just its parent, change its hash?
Git generates a SHA-1 (or SHA-256 in newer configurations) hash by hashing the commit object's actual content: the tree hash, the parent commit hash(es), author, committer, timestamp, and message, all together. Because the hash is a function of all of that content, changing literally anything, even something that seems unrelated to the commit's own changes, like which commit it's built on top of, produces a completely different commit object with a completely different hash. This is exactly why rebasing creates "new" commits with new hashes even if the actual code changes are identical, the parent changed, so the hash has to change too.
4. What is the .git folder, and what would happen if you deleted it?
The .git folder is where every single thing Git tracks actually lives, all commit objects, branches, tags, configuration, remotes, the staging area, everything. Your actual project files sitting in the working directory are completely separate from it. If you delete .git, you instantly lose all version history, all branches, all remotes, everything Git-related, but your current working directory files remain exactly as they were, just no longer tracked by Git at all, it becomes a regular, non-versioned folder.
5. What's the difference between Git being "distributed" versus something like old-school SVN being centralized?
In a centralized system like SVN, there's one authoritative central server holding the full history, and clients typically only have a working copy of the latest version, most operations (including viewing history) require talking to that server. In Git, every clone is a full, complete copy of the entire repository history, not just the latest snapshot, which means you can commit, branch, view history, and do almost everything entirely offline, and there's no single "the" server, any clone could theoretically become the new source of truth if needed.
GitHub Specific
1. What's the difference between a fork and a branch, and when would you use each?
A branch lives inside the same repository and is typically used when you already have write access, teammates working on the same codebase branch off to build features in parallel. A fork creates an entirely separate copy of the repository under your own account, used when you don't have write access to the original repo, which is the standard way open source contributions work, fork the project, make changes on a branch in your fork, then open a pull request back to the original repository.
2. What is a pull request, and what's actually happening under the hood when you open one?
A pull request is a request to merge changes from one branch (often on a fork) into another branch, typically the original project's main branch, along with a space for discussion, code review comments, and automated checks (CI) to run against the proposed changes before anything actually gets merged. Under the hood, GitHub is really just comparing two branches (or a branch and a fork's branch) and tracking that comparison as an object with its own metadata, comments, and status checks, the actual merge doesn't happen until someone approves and clicks merge, or it's done via the CLI.
3. What's the difference between merge, squash and merge, and rebase and merge as GitHub's PR merge options?
Merge creates a standard merge commit, preserving the full commit history of the PR branch exactly as it happened. Squash and merge combines every commit in the PR into a single new commit on the target branch, keeping the target branch's history clean at the cost of losing the PR's internal commit-by-commit history (though it's still visible on the closed PR itself). Rebase and merge replays each of the PR's individual commits directly onto the target branch without a merge commit, keeping a linear history while still preserving each individual commit, a middle ground between the other two options.
4. What are GitHub Actions, and how is a workflow file structured at a high level?
GitHub Actions is GitHub's built-in CI/CD system, letting you automatically run scripts, tests, deployments, in response to events in your repository (a push, a PR being opened, a scheduled time). A workflow is defined in a YAML file under .github/workflows/, specifying triggers (on:), one or more jobs, each job running on a specified runner (like ubuntu-latest) and consisting of a sequence of steps, which can either run shell commands directly or use pre-built, reusable "actions" from the marketplace.
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm test
5. What's the difference between a GitHub Issue and a Pull Request?
An Issue is a tracked discussion item, a bug report, a feature request, a task, it has no code attached to it directly, it's purely for tracking and discussion. A Pull Request is specifically about proposing an actual code change, comparing two branches and including the real diff, plus it goes through code review and CI checks before it can be merged. Issues and PRs can reference each other though, a common convention is writing "Fixes #42" in a PR description to automatically link it to (and close) the related issue once merged.
6. What is branch protection, and what does it typically enforce?
Branch protection rules restrict what can happen directly on important branches, usually main or master, to prevent accidental or unreviewed changes from landing there. Common rules include requiring at least one approving review before merging, requiring status checks (like CI tests) to pass first, disallowing direct pushes entirely so all changes must go through a PR, and preventing force pushes or branch deletion, which protects the project's history from being accidentally rewritten or destroyed.
Workflows and Best Practices
1. What's the difference between Git Flow and trunk-based development, and why have a lot of teams moved toward trunk-based?
Git Flow uses several long-lived branches, develop, feature branches, release branches, hotfix branches, with a fairly rigid, structured process for how code moves between them before reaching main. Trunk-based development keeps a single main branch that everyone commits to frequently (often with short-lived feature branches merged quickly, sometimes behind feature flags), avoiding long-lived branches that drift far apart and become painful to merge. A lot of teams moved toward trunk-based because Git Flow's long-lived branches tend to accumulate large, painful merge conflicts, and it doesn't pair well with modern continuous deployment, where you want changes integrated and tested constantly, not batched up for a big release branch.
2. What makes a good commit message, and why does it actually matter beyond just "documentation"?
A good commit message has a short, clear summary line (often under about 50 characters) describing what changed, and if needed, a more detailed body explaining why the change was made, not just what, since the diff itself already shows what changed. It matters practically because git log, git blame, and tools that generate changelogs all depend on commit messages being useful, and six months later when you're trying to figure out why a weird piece of code exists, a good commit message can save you real debugging time that a vague "fix stuff" message never will.
3. What is a .gitignore file, and what happens if you add a file to it after that file's already been committed?
.gitignore tells Git which files or patterns to never track, generated build output, dependency folders like node_modules, environment files with secrets, editor config files, so they never accidentally get staged or committed. If a file is already tracked (already committed at some point in the past), adding it to .gitignore after the fact does nothing on its own, Git keeps tracking it since it's already in the index, you have to explicitly remove it from tracking with git rm --cached <file> (keeping it on disk) before .gitignore will actually start ignoring future changes to it.
4. How would you handle a large binary file, like a video or a dataset, in a Git repo, and why is committing it directly usually a bad idea?
Git is built around tracking text-based diffs efficiently, but binary files can't be meaningfully diffed the same way, so every version of a large binary file gets stored close to in full, which bloats the repository's size fast and makes cloning painfully slow over time, since every clone downloads the entire history including every old version of that file. The standard fix is Git LFS (Large File Storage), which stores the actual binary content outside the main Git history and only keeps a small pointer/reference inside the repo itself, keeping the actual Git history lightweight.
5. What's a monorepo, and what are the tradeoffs versus multiple separate repos?
A monorepo keeps multiple projects or services in a single repository, instead of splitting each into its own separate repo. It makes cross-project changes atomic (one commit can update a shared library and every service that uses it together), simplifies dependency management, and gives you a single source of truth for the whole codebase's history. The tradeoffs are real too though, repository size and clone times grow, CI needs to be smart enough to only build/test what actually changed, and access control becomes coarser since it's harder to restrict who can see which specific project inside one giant repo.
Troubleshooting and Real-World Scenarios
1. You accidentally committed a secret (an API key) to a public repo. What do you actually need to do, and why isn't deleting it in a new commit enough?
Deleting it in a new commit only removes it going forward, the secret is still sitting there in the repository's history, anyone can check out that old commit and grab it, and worse, if the repo is public, bots actively scan GitHub for exposed credentials within minutes. The real fix has two parts: first, treat the secret as compromised and rotate/revoke it immediately at the source, that's non-negotiable regardless of what you do to the repo. Second, actually remove it from history using a tool like git filter-repo (or BFG Repo-Cleaner) to rewrite every commit that ever contained it, then force-push the cleaned history, understanding that anyone who already cloned the repo still has the old history with the secret in it.
2. You're mid-rebase and it's a mess. How do you safely bail out and get back to where you started?
git rebase --abort immediately stops the rebase and restores your branch to exactly the state it was in before you started, as if the rebase never happened, this is the safe escape hatch and it's always worth remembering when a rebase starts going sideways with conflict after conflict. If you're stuck resolving a specific conflict step but want to skip that one commit's changes entirely, git rebase --skip handles that instead, while git rebase --continue moves forward once you've actually resolved the current conflict.
3. Your branch has diverged badly from main with tons of conflicts. What's a sane way to approach resolving that?
Rather than doing one giant merge and getting hit with every conflict across your entire branch's history at once, it's often easier to merge (or rebase onto) main early and often as you go, so conflicts stay small and manageable instead of compounding. If you're already deep in a badly diverged branch, an interactive rebase onto main lets you resolve conflicts commit by commit instead of all at once, which is mentally much easier to reason about than one massive merge conflict touching dozens of files simultaneously.
4. How do you find which commit introduced a specific bug in a large history?
git bisect does a binary search through your commit history, you tell it a known-good commit and a known-bad commit, and it checks out commits in between, you test each one and mark it good or bad, and it narrows down to the exact commit that introduced the problem in roughly log(n) steps instead of manually checking every commit one by one.
git bisect start
git bisect bad # current commit is broken
git bisect good v1.2.0 # this old tag was fine
# Git checks out a commit in between, you test it, then:
git bisect good # or
git bisect bad
# repeat until Git identifies the exact bad commit
git bisect reset # when done
5. What's the difference between deleting a branch locally versus on the remote, and why does git branch -d sometimes fail to delete a branch?
Deleting locally (git branch -d branch-name) only removes the pointer on your own machine, the remote copy (if it exists) is completely untouched, you need a separate command (git push origin --delete branch-name) to remove it from the remote too, they're independent operations. git branch -d fails specifically when Git can't confirm the branch has been fully merged into your current branch, which is a deliberate safety check, if you're sure you want to delete it anyway (say, it was already merged via a squash merge on GitHub, which Git's local merge-detection doesn't recognize as "merged"), you'd need git branch -D to force it.
Struggling to Find a Job? Get Specific Batch Wise job Updates ✅ Check now