Git
Git & Version Control — engineer-style answers. Every answer is written the way a senior Cloud/DevOps engineer actually reasons in an interview: clarify first, avoid assumptions, look for evidence, act safely, communicate clearly, and prevent recurrence. Strong engineers don't just troubleshoot faster — they eliminate uncertainty faster.
Table of Contents
Section 1 — Core Git (Q1–Q25)
- What is version control? Centralized vs distributed
- Git architecture — working directory, staging, local, remote
- Git init, clone, config — setting up repos and identity
- Git add, commit, status, log — basic workflow
- Git diff — comparing working directory, staging, commits
- Interactive staging (git add -p) — partial changes
- Branches — create, switch, list, delete
- Git merge — fast-forward, 3-way, merge commits
- Git rebase — linear history, interactive, squashing
- Rebase vs merge — when to use, golden rule
- Git remote — origin, upstream, fetch, pull, push
- Git pull vs fetch — pull --rebase
- Git stash — save, apply, pop, list, drop
- .gitignore — patterns, global, already-tracked files
- Git tags — lightweight, annotated, semver
- Git log — formatting, filtering, --oneline, --graph
- HEAD, detached HEAD — meaning, recovery
- Git reset — soft, mixed, hard
- Git revert — safe undo without rewriting history
- Git cherry-pick — apply specific commits
- Git blame and bisect — who changed, when bugs appeared
- Git clean and restore — untracked files, restoring
- SSH keys and HTTPS — authentication
- Pull requests / merge requests — code review
- Git aliases and configuration — shortcuts
Section 2 — Intermediate & Workflows (Q26–Q46)
- Branching strategies — Git Flow, GitHub Flow, trunk-based
- Trunk-based development — short-lived branches, feature flags
- Conventional commits — standards, changelog generation
- Merge strategies — merge commit, squash, rebase merge
- Git hooks — pre-commit, commit-msg, pre-push, server-side
- Branch protection — reviews, status checks, signed commits
- Git submodules — dependencies, updating, pitfalls
- Git subtree — alternative to submodules
- Monorepo vs polyrepo — trade-offs, tooling
- Git LFS — binary files, storage, bandwidth
- Git rebase --onto — advanced rebasing
- Git reflog — recovering lost commits
- Git worktree — multiple working directories
- Signed commits and tags — GPG/SSH signing
- Git secrets and security — scanning, hooks
- GitHub Actions / GitLab CI — pipelines from Git events
- Code owners — CODEOWNERS, auto review assignment
- Release management — tagging, changelogs, semantic release
- Git in CI/CD — shallow clone, sparse checkout, performance
- Forking workflow — open source, upstream sync
- GitHub vs GitLab vs Bitbucket — comparison
Section 3 — Advanced & Internals (Q47–Q57)
- Git rerere — reuse recorded conflict resolution
- Git patch and format-patch — sharing changes
- Git internals — objects, SHA-1, packfiles
- Garbage collection — gc, prune, pack, dangling
- Monorepo at scale — sparse checkout, partial clone
- Git server administration — bare repos, hooks, access
- Platform migration — GitHub↔GitLab, SVN to Git
- Git and compliance — audit trails, traceability
- Rewriting history — filter-repo, BFG
- Performance at scale — fsmonitor, maintenance
- GitOps — Git as source of truth for infra
Section 4 — Incident & Troubleshooting Scenarios (Q58–Q80)
- Merge conflict in critical production hotfix
- Accidentally committed secrets to repo
- Force push overwrote teammate's commits
- Detached HEAD — commits lost
- git reset --hard lost uncommitted work
- Merge produced broken build — 3 days unnoticed
- Repo is 10GB — clone takes 30 minutes
- Rebase conflict hell — 50 conflicts
- Wrong commit on main — revert vs reset
- Git bisect finds bug 200 commits ago
- Submodule stuck on wrong commit
- CI fails only on main — works on feature branches
- Committed to wrong branch — move commits
- Deleted branch with unmerged work — recovery
- 200 tiny commits — clean before merge
- Two devs rebased same branch — duplicates
- Hotfix needed but main has unreleased features
- .gitignore not working — file already tracked
- Reverted merge commit, can't re-merge branch
- Large binary committed — repo bloated permanently
- Git pull creates unexpected merge commits
- Feature branch 3 months old, 500 behind main
- Same conflict appearing repeatedly
Section 1 — Core Git (Q1–Q25)
Q1. What is version control? Centralized vs distributed
Scenario: A new team is choosing tooling for a service that several engineers will edit daily, and someone asks why Git instead of a shared network drive or SVN.
Answer: Version control is a system that records changes over time so you can answer who changed what, when, and why, and roll back safely. The distinction I'd frame is failure model, not features. Centralized (SVN, Perforce) keeps the full history on one server — simple mental model, but the server is a single point of failure and most operations need network. Distributed (Git) gives every clone the full history, so commits, branches, and history browsing work offline, and every clone is effectively a backup. I'd pick Git in almost all cases because branching is cheap, operations are local/fast, and the ecosystem (GitHub/GitLab) makes review and CI frictionless. The one place I'd pause is very large binary assets, where Perforce or Git LFS matters.
Key Interview Signal: Frames the choice around failure modes and trade-offs, not textbook definitions.
Q2. Git architecture — working directory, staging, local, remote
Scenario: A junior asks why git add and git commit are two separate steps, and why their change "isn't on GitHub yet" after committing.
Answer: Git has four areas, and most confusion comes from not knowing which one you're in. The working directory is your actual files. The staging area (index) is a curated set of changes you've marked for the next commit — this is what lets you commit part of your work cleanly. The local repository (.git) holds the full committed history. The remote is a shared copy for collaboration. So the two-step add/commit exists on purpose: staging lets you craft focused commits instead of dumping everything. And a commit is local until you push, which is why it's not on GitHub yet. When I debug someone's "lost" change, the first question is always which of these four areas is it in — that eliminates half the uncertainty immediately.
Key Interview Signal: Uses the four-area model as a diagnostic tool, not just a diagram.
Q3. Git init, clone, config — setting up repos and identity
Scenario: Commits in CI are showing up authored by "root" or a wrong email, breaking blame and compliance reports.
Answer: git init creates a fresh repo; git clone copies an existing remote with full history and wires up origin. The part that actually causes production pain is identity config. git config user.name/user.email sets who authored commits — and if it's wrong on a shared machine or CI runner, blame and audit trails become useless. I set global identity per user, but in CI I set it explicitly per job so commits are attributable to the automation, not root. I'd also confirm whether user.email matches the platform account, otherwise commits won't link to the right profile. For anything compliance-sensitive I'd add commit signing on top. The fix here isn't just correcting one commit — it's making identity config a standard part of runner bootstrap so it can't drift.
Key Interview Signal: Connects a config detail to compliance/attribution and prevents recurrence.
Q4. Git add, commit, status, log — basic workflow
Scenario: A teammate's commits are huge, mix unrelated changes, and have messages like "fix stuff," making review and rollback painful.
Answer: The core loop is status to see state, add to stage intentionally, commit to record, log to review. The mechanics are trivial; the engineering judgment is commit hygiene. I aim for small, focused commits that each represent one logical change, with a message that explains why, not what (the diff already shows what). That pays off directly during incidents: small commits make git bisect, revert, and code review fast and low-risk. If I saw the "fix stuff" pattern, I wouldn't just nag — I'd introduce a commit-message convention and a commit-msg hook so the standard is enforced, not hoped for.
Key Interview Signal: Ties commit discipline to incident response and automates the standard.
Q5. Git diff — comparing working directory, staging, commits
Scenario: Before pushing a config change to a production service, you want to be certain exactly what's changing.
Answer: The trap with git diff is not knowing what it's comparing. Plain git diff is working directory vs staged. git diff --staged is staged vs last commit — that's what I check right before committing. git diff main..feature compares branches. Before a risky push I always run the staged diff so there are no surprises in the commit, and I diff against the deploy target branch to see the true delta going to prod. For big diffs, --stat first for the shape, then drill in. This is a small habit that prevents a whole class of "I didn't realize that file was included" incidents.
Key Interview Signal: Treats diff as a pre-deploy verification step, and knows precisely what each form compares.
Q6. Interactive staging (git add -p) — partial changes
Scenario: You fixed a bug but also left some debug prints and an unrelated refactor in the same file, and you need a clean commit for review.
Answer: git add -p lets me stage hunk-by-hunk so a single messy working file becomes several clean, logical commits. In practice I use it constantly to split "I was in flow and did three things" into reviewable pieces and to keep debug/experimental lines out of the commit. It also forces me to re-read my own diff, which catches accidental changes and secrets before they're staged. If a hunk is too coarse, I split it (s) or edit it (e). The payoff is that reviewers see intent-sized commits and rollback stays surgical.
Key Interview Signal: Uses partial staging for commit clarity and as a last-chance review of the diff.
Q7. Branches — create, switch, list, delete
Scenario: The repo has 200 stale branches and nobody's sure which are safe to delete.
Answer: A branch is just a movable pointer to a commit — that's why creating one is instant and cheap. git switch -c, git branch, git branch -d/-D. The operational issue in the scenario is branch sprawl, and the risk is deleting unmerged work. So I distinguish -d (safe, refuses if unmerged) from -D (force). To clean up safely I'd list branches already merged into main (git branch --merged main) and delete those; anything unmerged gets confirmed with the owner first. I'd also prune remote-tracking refs and, going forward, enable auto-delete of branches on merge in the platform so cleanup doesn't rely on discipline.
Key Interview Signal: Deletes safely by checking merged state and automates prevention.
Q8. Git merge — fast-forward, 3-way, merge commits
Scenario: Your team debates whether merges should always create a merge commit or fast-forward.
Answer: Fast-forward happens when the target hasn't diverged — Git just moves the pointer forward, no merge commit, perfectly linear. A 3-way merge happens when both branches advanced; Git creates a merge commit joining the two histories. Neither is "correct" universally. Merge commits preserve the true shape of collaboration and make it obvious what landed as a unit, which I like for release branches. --ff-only keeps history linear and is great for keeping a feature current with main. My rule of thumb: preserve context where it matters (integration points), keep it linear where noise hurts (local feature updates). What I avoid is treating this as dogma without knowing the team's review and revert workflow.
Key Interview Signal: Explains both mechanics and picks based on workflow, not preference.
Q9. Git rebase — linear history, interactive, squashing
Scenario: Before opening a PR, you want to clean up 12 work-in-progress commits into a coherent story.
Answer: Rebase replays commits onto a new base, which does two useful things: keeps a feature branch current on top of main (linear history) and, interactively (rebase -i), lets me squash, reorder, reword, or drop commits before review. I use it to turn messy WIP into a clean, reviewable sequence. The critical caveat is that rebase rewrites commit hashes — so I only rebase commits that haven't been shared. Rebasing shared history forces everyone else into painful recovery. So: rebase freely on my own local branch, never on main or anything teammates have pulled.
Key Interview Signal: Loves rebase for cleanup but names the shared-history danger explicitly.
Q10. Rebase vs merge — when to use, golden rule
Scenario: Interviewer asks for your one-line rule so juniors don't corrupt shared history.
Answer: Both integrate changes; the difference is history shape and safety. Merge preserves history and is non-destructive — safe for shared branches. Rebase rewrites history for a clean linear log — great locally, dangerous on anything shared. The golden rule I give the team: never rebase commits that exist outside your local repo (i.e., anything already pushed and used by others). Practical policy I like: rebase your feature branch onto main to stay current and clean, then merge into main via PR. That gives clean feature history plus a safe, auditable integration point. The trade-off I weigh is linear-log readability vs preserving exactly how integration happened — for auditability I lean toward keeping merge commits at integration.
Key Interview Signal: Delivers the golden rule crisply and pairs it with a real policy.
Q11. Git remote — origin, upstream, fetch, pull, push
Scenario: A contributor forked a repo and is confused about how to get the latest upstream changes.
Answer: A remote is just a named URL. origin is conventionally your own fork/clone; upstream is the original repo you forked from. fetch downloads remote changes without touching your working files; pull fetches then merges; push sends your commits up. In a fork workflow I add upstream, fetch upstream, and rebase or merge upstream/main into my branch to stay current, while I only push to origin. The mistake I watch for is people pulling into the wrong remote or force-pushing to a shared branch. Being explicit about which remote is which removes most of that confusion.
Key Interview Signal: Clearly separates origin vs upstream and fetch vs pull's side effects.
Q12. Git pull vs fetch — pull --rebase
Scenario: Your history is littered with "Merge branch 'main' into 'main'" commits and nobody wants them.
Answer: fetch is read-only — it updates remote-tracking refs so I can inspect before integrating; I prefer fetch-then-look on anything sensitive. pull is fetch + merge in one step, which is where the noisy merge commits come from when local and remote both advanced. git pull --rebase instead replays my local commits on top of the fetched ones, giving a clean linear history without those merge bubbles. I set pull.rebase=true (or pull.ff=only) as a default to prevent the mess, while being aware that rebasing local-but-unpushed commits is fine and expected. If someone hits conflicts during the rebase, that's just their local commits meeting reality — resolve and continue.
Key Interview Signal: Knows pull's hidden merge behavior and fixes it with config, not manual cleanup.
Q13. Git stash — save, apply, pop, list, drop
Scenario: You're mid-feature when a production hotfix comes in and you need a clean tree now.
Answer: Stash shelves uncommitted work so I can switch context with a clean tree. stash push -m "msg" (I always label them), stash list, stash apply (keeps the stash) vs pop (applies and drops), stash drop. For the hotfix scenario I stash, branch off main, fix, then come back and pop. Two gotchas I flag: stash doesn't include untracked files unless I add -u, and a stash is easy to forget and lose — so for anything important I'd rather make a quick WIP commit on a branch than trust a stash. Stash is for short, tactical context switches, not storage.
Key Interview Signal: Uses stash correctly but knows its limits (untracked files, forgettability).
Q14. .gitignore — patterns, global, already-tracked files
Scenario: Someone added .gitignore for node_modules, but it's still showing up in every commit.
Answer: .gitignore uses glob patterns to keep untracked files out of Git. The classic failure — exactly this scenario — is that .gitignore only affects untracked files. If a file was already committed, ignoring it does nothing; I have to git rm --cached <path> to stop tracking it, then commit. I keep project-specific rules in the repo's .gitignore and personal/editor noise (.DS_Store, IDE files) in a global ignore so I'm not polluting shared config. For anything sensitive, ignore isn't a security control — secrets already committed are still in history and need real remediation. That "already-tracked" nuance is the single most common gotcha here.
Key Interview Signal: Nails the already-tracked gotcha and separates project vs global ignores.
Q15. Git tags — lightweight, annotated, semver
Scenario: Releases need to be reproducible and auditable, and "which commit is in prod?" keeps coming up.
Answer: A tag is a stable pointer to a specific commit — I use them to mark releases. Lightweight tags are just a name; annotated tags (-a) store tagger, date, message, and can be GPG-signed, so for releases I always use annotated (and often signed) tags for auditability. I follow SemVer (vMAJOR.MINOR.PATCH) so the version communicates the nature of the change. Tags must be pushed explicitly (git push --tags or push the specific tag). In practice the tag becomes the answer to "what's in prod" — CI builds and deploys from the tag, so the tag, the artifact, and the running version all line up. That traceability is the whole point.
Key Interview Signal: Uses annotated/signed tags for reproducible, auditable releases.
Q16. Git log — formatting, filtering, --oneline, --graph
Scenario: During an incident you need to find, fast, what merged into main in the last 6 hours.
Answer: git log is my primary investigation tool during incidents, and knowing the filters is what makes it fast. --oneline --graph --decorate gives a compact branch-aware view. --since/--until narrows the window; --author filters by person; -- <path> scopes to a file; -S"string" finds when a code string was introduced or removed (pickaxe). For the scenario I'd run git log --since="6 hours ago" --oneline main to see exactly what landed, then diff suspicious commits. The mindset is narrow the search space fast — good log filtering is how you eliminate uncertainty instead of scrolling.
Key Interview Signal: Treats log as an incident tool and knows the pickaxe/time filters.
Q17. HEAD, detached HEAD — meaning, recovery
Scenario: A junior checked out a commit hash, made a few commits, switched branches, and now their work "vanished."
Answer: HEAD is a pointer to your current position — normally to a branch. Detached HEAD means you checked out a specific commit directly, so HEAD points at a commit with no branch tracking it. Commits made there are real but unreferenced, so switching away appears to lose them. The recovery is calm and reliable: git reflog shows recent HEAD positions, I find the lost commit's hash, and git branch recovered <hash> to save it. Nothing is actually lost until garbage collection runs (weeks later). So my message to a panicked teammate is: don't force-push or reset anything, check reflog first. The prevention is: create a branch before experimenting on a raw commit.
Key Interview Signal: Stays calm, reaches for reflog, and reassures instead of thrashing.
Q18. Git reset — soft, mixed, hard
Scenario: You committed too early and want to restructure, but a teammate is about to run reset --hard on a shared branch.
Answer: Reset moves the branch pointer; the three modes differ in what they touch. --soft moves the pointer but keeps changes staged (great for redoing the last commit). --mixed (default) keeps changes in the working directory but unstaged. --hard discards everything back to that commit — destructive, and the one that eats uncommitted work. I use soft/mixed freely on local, unshared commits to reshape them. I never reset a shared branch, because it rewrites history others depend on — that's what revert is for. Before any --hard I confirm there's nothing uncommitted I care about, because that work isn't in reflog. So in the scenario I'd stop the teammate: on a shared branch, reset is the wrong tool.
Key Interview Signal: Separates the three modes precisely and refuses reset on shared history.
Q19. Git revert — safe undo without rewriting history
Scenario: A commit already on main and pulled by the whole team introduced a bug in prod.
Answer: git revert creates a new commit that undoes a previous one, without rewriting history. That's exactly what I want on shared branches: everyone's history stays valid, the undo is itself auditable, and CI/CD can roll forward cleanly. Contrast with reset, which rewrites history and would break everyone downstream. So for the scenario — bad commit already public — revert is the safe, correct undo. If it's a merge commit, I revert with -m 1 to specify the mainline parent, and I note that re-merging the branch later needs care (reverting the revert). Revert is my default "undo in production" because it's non-destructive and traceable.
Key Interview Signal: Chooses revert for shared history and knows the -m merge nuance.
Q20. Git cherry-pick — apply specific commits
Scenario: A bug is fixed on develop, but you need only that fix on the release branch without pulling everything else.
Answer: cherry-pick applies a specific commit onto my current branch as a new commit. The canonical use is a targeted hotfix: pull one fix into a release branch without dragging along unrelated in-progress work. git cherry-pick <hash> (or a range). Two things I keep in mind: it creates a new commit with a different hash, so the same change now exists in two places — if both branches later merge, that can cause duplicate-commit noise or conflicts, which I resolve with careful merging or rerere. And I make sure I'm picking the complete fix, not one commit of a multi-commit change. Used surgically it's perfect; overused it fragments history.
Key Interview Signal: Uses cherry-pick for targeted backports and anticipates duplicate-commit fallout.
Q21. Git blame and bisect — who changed, when bugs appeared
Scenario: A bug appeared "sometime in the last 300 commits" and manual review would take a day.
Answer: These answer two different questions. git blame <file> tells me who last changed each line and in which commit — my starting point for "why is this line like this" and who to ask. git bisect finds which commit introduced a bug via binary search: I mark a known-good and known-bad commit, and Git checks out midpoints for me to test, halving the range each step — 300 commits becomes ~9 tests. If I have an automated test, git bisect run <script> fully automates it. My approach: reproduce reliably first (bisect is only as good as the test), then let bisect eliminate the search space instead of guessing. Blame for who, bisect for when.
Key Interview Signal: Uses bisect to collapse a huge search space and stresses a reliable repro.
Q22. Git clean and restore — untracked files, restoring
Scenario: A build left hundreds of untracked artifacts, and someone wants to wipe them — on a machine that also has real uncommitted work.
Answer: git clean removes untracked files — dangerous because those files are not in Git and not recoverable. So I always dry-run first: git clean -nd to see exactly what would be deleted, then -fd only after I've read the list. git restore is the modern way to discard changes to tracked files: restore <file> reverts working-dir changes, restore --staged <file> unstages. In the scenario, the risk is nuking real work that happens to be untracked, so the dry run isn't optional. I'd also make sure build artifacts are .gitignored and cleaned by the build tool, so nobody has to reach for git clean in the first place.
Key Interview Signal: Always dry-runs destructive clean and distinguishes tracked vs untracked recovery.
Q23. SSH keys and HTTPS — authentication
Scenario: CI can't push, developers hit random auth prompts, and there's talk of committing a token to make it "just work."
Answer: Two auth methods: HTTPS (username + personal access token, easy through firewalls, good for CI with a scoped token in a secrets store) and SSH (key pair, no password prompts once set up, great for daily dev). The anti-pattern I'd shut down immediately is committing a token — secrets never go in the repo; they go in the CI secrets manager or an SSH deploy key with least privilege. For the CI failure I'd check: is the key/token present in the runner, is it expired, does it have the right scope/repo access, and is known_hosts set. For humans I prefer SSH keys or a credential helper so they stop getting prompted. Principle throughout: scoped, rotatable credentials, never in source.
Key Interview Signal: Refuses secrets-in-repo and reasons about scope, rotation, and least privilege.
Q24. Pull requests / merge requests — code review
Scenario: Bugs keep reaching main despite "we do PRs," and reviews are rubber-stamp approvals.
Answer: A PR/MR is a proposal to merge a branch, and it's really a quality gate, not a formality. The mechanics (open, review, discuss, merge) matter less than what gates it: required approvals, passing CI status checks, up-to-date-with-base, and ideally CODEOWNERS review for sensitive paths. If bugs still slip through, "we do PRs" isn't the fix — I'd look at whether CI actually runs meaningful tests as a required check, whether PRs are small enough to review properly, and whether approvals are enforced by branch protection rather than culture. I'd also add automated checks (lint, security scan, coverage) so humans review design and logic, not style. The goal is to make the safe path the default path.
Key Interview Signal: Treats PRs as enforced gates and diagnoses why review isn't catching bugs.
Q25. Git aliases and configuration — shortcuts
Scenario: The team runs the same long git log --graph --oneline --decorate --all twenty times a day, with typos.
Answer: Aliases (git config --global alias.lg "log --graph --oneline --decorate --all") turn repeated commands into safe, consistent shortcuts, which reduces typos and cognitive load. I set common ones (co, st, lg, last) and shell-out aliases with ! for scripted workflows. Beyond convenience, config is where I standardize team behavior: pull.rebase, push.default=current, rerere.enabled, default branch name, editor, and signing. The real value is consistency — I'd share a baseline config so everyone gets the same safe defaults instead of each person's machine behaving differently. Config drift between machines is a quiet source of "works for me" problems.
Key Interview Signal: Uses config/aliases to standardize safe team defaults, not just save keystrokes.
Section 2 — Intermediate & Workflows (Q26–Q46)
Q26. Branching strategies — Git Flow, GitHub Flow, trunk-based
Scenario: A team ships to prod daily but is using heavyweight Git Flow with develop, release, and hotfix branches, and integration is painful.
Answer: There's no universally best strategy — it depends on release cadence. Git Flow (feature/develop/release/hotfix) suits versioned, scheduled releases like packaged software, but it's heavy and causes long-lived branches that drift and cause merge pain. GitHub Flow is lightweight — branch off main, PR, merge, deploy — good for continuous delivery. Trunk-based development keeps everyone integrating to main constantly with very short-lived branches, which minimizes merge hell and suits high-frequency deploys. For a team shipping daily, Git Flow is the wrong fit — the long-lived branches fight their cadence. I'd move them toward GitHub Flow or trunk-based, because the strategy should reduce integration friction, not add ceremony. I always match the branching model to how often they actually release.
Key Interview Signal: Matches strategy to release cadence and diagnoses the mismatch.
Q27. Trunk-based development — short-lived branches, feature flags
Scenario: Leadership wants trunk-based dev but is worried about shipping half-finished features to main.
Answer: Trunk-based means everyone commits to main (or via very short-lived branches merged within a day or two), which keeps integration continuous and merge conflicts small. The obvious tension — half-done features on main — is solved with feature flags: incomplete code is merged but disabled, so main is always releasable and features are turned on when ready, gradually, and can be killed instantly if they misbehave. This also decouples deploy from release. The requirements to make it safe: strong automated tests as a required gate, fast CI, and flag hygiene (a plan to remove stale flags). The trade-off is added flag complexity and discipline, but in return you get smaller merges, faster feedback, and safer rollouts. It's my default for teams that deploy frequently.
Key Interview Signal: Solves the "unfinished on main" fear with feature flags and names flag hygiene.
Q28. Conventional commits — standards, changelog generation
Scenario: Release notes are hand-written, inconsistent, and version bumps are argued about every release.
Answer: Conventional Commits is a structured message format — type(scope): description with types like feat, fix, chore, and BREAKING CHANGE. The payoff is automation: tooling parses commit types to auto-generate changelogs and decide the SemVer bump (fix→patch, feat→minor, breaking→major). That kills the manual release-notes work and the version arguments in the scenario. To make it stick I enforce it with a commit-msg hook or commitlint in CI rather than relying on memory, and wire it to semantic-release so tagging, changelog, and publish are automatic. The trade-off is a small discipline cost on every commit, but it turns releases from a manual chore into a byproduct of normal work.
Key Interview Signal: Connects commit format to automated versioning and enforces it in CI.
Q29. Merge strategies — merge commit, squash, rebase merge
Scenario: Your PR has 30 noisy WIP commits and the team debates how it should land on main.
Answer: Three options with different history outcomes. Merge commit preserves every commit plus a merge node — full context, but noisy if the branch was messy. Squash merge collapses the whole PR into one clean commit on main — great when the intermediate commits are WIP noise, and it makes revert trivial (one commit), at the cost of losing granular history. Rebase merge replays commits linearly with no merge node — clean and linear but rewrites hashes. For the 30-noisy-commits case I'd squash, because main should read as a series of meaningful changes, not someone's WIP diary, and one-commit-per-feature makes rollback clean. When intermediate commits are genuinely meaningful (e.g., a curated refactor), I'd keep them. I pick per-PR based on whether the history has value.
Key Interview Signal: Picks squash for noisy PRs and ties the choice to revert simplicity.
Q30. Git hooks — pre-commit, commit-msg, pre-push, server-side
Scenario: Broken lint and accidental secrets keep reaching CI, wasting pipeline minutes and exposing risk.
Answer: Hooks run scripts on Git events. pre-commit runs before a commit — I use it for fast checks: lint, format, secret scanning — to catch problems at the earliest, cheapest point. commit-msg enforces message conventions. pre-push runs heavier checks (tests) before code leaves the machine. The catch: client-side hooks live locally and aren't shared or guaranteed, so I manage them with a framework like pre-commit and, critically, I duplicate the important checks server-side — as required CI status checks or server-side hooks — because anything security-relevant can't depend on a developer having the hook installed. So hooks are a fast local feedback loop, but the server-side gate is the real enforcement. For the scenario: pre-commit secret scan for speed, plus a CI gate that can't be bypassed.
Key Interview Signal: Uses hooks for fast feedback but insists real enforcement lives server-side.
Q31. Branch protection — reviews, status checks, signed commits
Scenario: Someone force-pushed to main and rewrote history; leadership wants it to be impossible.
Answer: Branch protection is the platform-level guardrail on important branches. I'd enable: require PRs (no direct pushes), require N approvals, require passing status checks, require branch up-to-date before merge, block force-pushes and deletions, and for high-security repos require signed commits and CODEOWNERS review. That directly prevents the scenario — force-push to main becomes impossible, not just discouraged. The principle is defense in depth: hooks and culture help, but protection rules are enforced by the server and can't be bypassed by a mistaken local command. I also make sure admins aren't silently exempt if the compliance requirement is strict. The trade-off is a bit of friction, which is exactly the point on a production branch.
Key Interview Signal: Makes the dangerous action impossible at the server, not just discouraged.
Q32. Git submodules — dependencies, updating, pitfalls
Scenario: After a fresh clone, half the team's build fails because a shared library folder is empty.
Answer: A submodule embeds one repo inside another, pinned to a specific commit. The empty-folder symptom is the classic pitfall: a plain git clone doesn't pull submodule contents — you need --recurse-submodules or git submodule update --init --recursive. Other sharp edges: the parent tracks a specific commit, so updating is a two-step dance (update inside the submodule, then commit the new pointer in the parent), and it's easy to end up with a detached HEAD or a stale/uncommitted pointer. Submodules give precise version pinning but add real cognitive overhead. For the scenario I'd fix the clone instructions/CI to recurse, and honestly I'd evaluate whether a package manager or subtree would be simpler for the team, because submodules trip people up constantly.
Key Interview Signal: Nails the recurse-clone gotcha and questions whether submodules are even the right tool.
Q33. Git subtree — alternative to submodules
Scenario: The team is tired of submodule friction and wants shared code that "just works" on clone.
Answer: git subtree merges another repo's history directly into a subdirectory of yours, so unlike submodules there are no extra clone steps or pointer commits — a normal clone gets everything, which fixes the biggest submodule pain. Contributors don't need to know a subtree exists. The trade-offs: the subtree commands (add/pull/push --prefix) are clunkier to remember, it bloats your repo with the vendored history, and pushing changes back upstream is more awkward than submodules. My rule: use subtree when consumers just need the code to be present with zero ceremony and updates are infrequent; use submodules when precise version pinning and clean separation matter more; and honestly, prefer a real package manager over both when the dependency is a proper library.
Key Interview Signal: Compares subtree vs submodule on the clone-simplicity vs pinning trade-off.
Q34. Monorepo vs polyrepo — trade-offs, tooling
Scenario: A company with 40 microservices in 40 repos struggles with cross-cutting changes and inconsistent CI.
Answer: Monorepo puts everything in one repo: atomic cross-service changes, one source of truth, easy shared tooling and refactors — but it needs serious tooling (build systems like Bazel/Nx, sparse checkout, CI that only builds what changed) or it gets slow and CI runs everything on every change. Polyrepo isolates each service: independent versioning and access control, simpler per-repo CI — but cross-cutting changes span many PRs and dependency/version drift is a constant tax, which is exactly the scenario's pain. There's no free lunch. For 40 tightly-coupled services doing frequent cross-cutting work, I'd lean monorepo if they invest in the tooling; if services are truly independent with separate teams, polyrepo is fine. The deciding factors are coupling, team boundaries, and willingness to invest in tooling.
Key Interview Signal: Weighs coupling and tooling investment rather than declaring a winner.
Q35. Git LFS — binary files, storage, bandwidth
Scenario: A repo with design assets and model files has ballooned to 8GB and clones are painfully slow.
Answer: Git is terrible with large binaries — every version is stored in full in history, so the repo bloats permanently and clones crawl, which is exactly what happened. Git LFS fixes this by storing large files on a separate LFS server and keeping only a small pointer in the repo, so clones are light and you fetch binaries on demand. I'd set up LFS with git lfs track "*.psd" etc. via .gitattributes before the files are added. The important caveat: LFS doesn't retroactively shrink history — files already committed the normal way are still bloating it, so I'd need git lfs migrate or filter-repo to rewrite history and truly reclaim space, which is a coordinated, disruptive operation. And I'd watch LFS bandwidth/storage quotas and costs. Prevention: enforce LFS tracking via hook so big binaries can't sneak in raw again.
Key Interview Signal: Knows LFS doesn't fix existing bloat and plans the history rewrite + prevention.
Q36. Git rebase --onto — advanced rebasing
Scenario: You branched a feature off the wrong base branch and now need to move just your commits onto the correct base.
Answer: git rebase --onto <newbase> <upstream> <branch> is the surgical version of rebase: it lets me transplant a specific range of commits onto a different base, rather than the whole branch. The classic use is exactly this — I branched off feature-a but should have branched off main, so rebase --onto main feature-a my-branch replays only my commits onto main, dropping the accidental feature-a commits. It's also how I split or extract a subset of commits. It's powerful but easy to get the arguments wrong, so I sanity-check with git log --graph before and after, and since it rewrites history I only do it on unshared commits. I'd rehearse it mentally or on a backup branch before running it on anything I care about.
Key Interview Signal: Explains the three-arg transplant precisely and verifies before/after.
Q37. Git reflog — recovering lost commits
Scenario: A teammate ran a bad reset --hard and is convinced days of work are gone.
Answer: Reflog is my safety net and the first thing I reach for when something "disappears." It records every movement of HEAD and branch tips locally — resets, rebases, checkouts, merges — even when those commits are no longer reachable from any branch. So after a bad reset --hard, git reflog shows the commit HEAD pointed at before the reset, and I recover with git reset --hard <hash> or git branch rescue <hash>. The key reassurance: Git almost never truly deletes commits immediately — they're just unreferenced and survive until garbage collection (default weeks). So my incident response is: stop, don't run more destructive commands, check reflog. The one thing reflog can't recover is work that was never committed (never left the working directory) — which is the argument for committing early and often.
Key Interview Signal: Reflog-first recovery, calm messaging, and knows its one blind spot (uncommitted work).
Q38. Git worktree — multiple working directories
Scenario: You're deep in a feature with a dirty tree when an urgent hotfix arrives, and you don't want to stash or lose context.
Answer: git worktree lets one repo have multiple working directories checked out to different branches simultaneously, sharing the same .git object store. So instead of stashing my dirty feature and switching branches, I git worktree add ../hotfix main, fix the hotfix in a separate folder with its own clean state, then remove the worktree — my feature work is untouched the whole time. It's cleaner than stash for parallel work, and it avoids re-cloning the whole repo just to work on two branches at once. I also use it to run a long build on one branch while coding on another. Gotcha: you can't check out the same branch in two worktrees, and I clean up stale worktrees with worktree prune.
Key Interview Signal: Reaches for worktree over stash/re-clone for genuine parallel work.
Q39. Signed commits and tags — GPG/SSH signing
Scenario: A security audit asks how you prove commits genuinely came from the claimed author.
Answer: By default Git author identity is just self-reported config — anyone can set any name and email, so it proves nothing. Signing fixes attribution: git commit -S and git tag -s cryptographically sign with a GPG or SSH key, and the platform shows a "Verified" badge when the signature matches a registered key. For the audit I'd require signed commits via branch protection, so unsigned commits can't merge to protected branches, and require signed release tags so the exact deployed artifact is provably from an authorized signer. Operationally the challenge is key management — distributing, registering, and rotating keys, and configuring CI bots to sign — so I'd automate that. The value is a real, verifiable chain of custody rather than trusting a text field.
Key Interview Signal: Explains why unsigned identity is worthless and enforces signing via protection.
Q40. Git secrets and security — scanning, hooks
Scenario: An API key was found in the repo history during a security review; you need to stop it happening again.
Answer: Two problems: remediate the leaked secret and prevent future leaks. For the leaked key, committing over it isn't enough — it's still in history, so I treat the key as compromised and rotate/revoke it immediately (that's step one, always), then scrub history with git filter-repo or BFG and force-push in a coordinated way. For prevention, defense in depth: a pre-commit secret scanner (gitleaks/trufflehog) for fast local blocking, a CI scan as the enforced gate that can't be skipped, and platform push protection / secret scanning enabled. I'd also make sure secrets live in a vault/secrets manager and are injected at runtime, so there's nothing to commit in the first place. The mindset: assume any committed secret is already leaked, rotate first, scrub second, prevent third.
Key Interview Signal: Rotates first (assumes compromise), then scrubs, then automates prevention.
Q41. GitHub Actions / GitLab CI — pipelines from Git events
Scenario: You need CI to run tests on every PR and deploy only when a version tag is pushed.
Answer: These trigger pipelines from Git events defined as YAML in the repo. I map events to workflows: run lint/tests on pull_request and pushes to feature branches (fast feedback, required as a status check), and gate deploys on specific events like a push to main or a version tag. For the scenario: a test workflow on pull_request, and a deploy workflow that runs only on push of tags matching v*. Things a good engineer wires in: caching for speed, least-privilege credentials from the secrets store (never hardcoded), environment protection/approvals for prod, and matrix builds where needed. Common failure modes I watch for: leaked secrets in logs, flaky tests blocking merges, and over-broad triggers running everything on every change. The pipeline is code, so it gets reviewed and versioned like any other.
Key Interview Signal: Maps events to stages, gates prod, and treats pipeline security/perf as first-class.
Q42. Code owners — CODEOWNERS, auto review assignment
Scenario: Changes to the auth module keep getting merged without the security team seeing them.
Answer: A CODEOWNERS file maps paths to owning individuals/teams, and combined with branch protection it requires those owners to review changes touching their paths — and auto-assigns them as reviewers. For the scenario I'd add a rule so auth/** requires the security team's approval, and enable "require review from code owners" in branch protection so it can't be merged without them. This scales review knowledge — the right experts are pulled in automatically instead of relying on someone remembering to tag them. I'd keep the file granular enough to be useful but not so granular it creates bottlenecks, and make sure owner teams are staffed so they don't become a merge blocker. It turns "someone should have looked at this" into an enforced rule.
Key Interview Signal: Turns tribal knowledge of "who should review" into an enforced, auto-assigned rule.
Q43. Release management — tagging, changelogs, semantic release
Scenario: Releases are manual, error-prone, and nobody can reliably say what changed between two versions.
Answer: I want releases to be automated, reproducible, and traceable rather than a manual ritual. The pipeline I'd build: enforce Conventional Commits, then use semantic-release (or similar) to automatically determine the next SemVer version from commit types, generate the changelog, create a signed annotated tag, and publish the artifact — all triggered from a merge to main. The tag becomes the single source of truth: CI builds and deploys from the tag, so version, artifact, and running code all line up, and "what changed between v1.4 and v1.5" is answered by the generated changelog. This removes human error from version bumps and release notes, and makes rollback to a known tag trivial. The trade-off is upfront pipeline setup and commit discipline, which pays for itself by the second release.
Key Interview Signal: Makes releases an automated byproduct of merges, fully traceable via tags.
Q44. Git in CI/CD — shallow clone, sparse checkout, performance
Scenario: CI jobs spend 4 minutes just cloning a large monorepo before any real work starts.
Answer: CI usually doesn't need full history or the whole repo, so I optimize the checkout. A shallow clone (--depth 1) fetches only the latest commit(s), which massively cuts clone time and bandwidth for jobs that just build/test the current state. For a big monorepo, sparse checkout (or partial clone with --filter=blob:none) pulls only the paths a given job needs. That directly attacks the 4-minute clone in the scenario. The caveats I keep in mind: shallow clones break operations that need history (blame across time, describe, some deploy tooling that reads tags) — so where history is needed I fetch more selectively rather than everything. I'd also cache dependencies separately from the checkout. Net: fetch the minimum each job actually requires, and measure the before/after so the optimization is evidence-based.
Key Interview Signal: Fetches the minimum per job and knows what shallow/sparse breaks.
Q45. Forking workflow — open source, upstream sync
Scenario: You're contributing to an open-source project and your fork has drifted far behind the upstream repo.
Answer: In the forking workflow, external contributors don't get push access to the main repo — they fork it, push to their own fork (origin), and open a PR back to the original (upstream). To fix drift, I add the original as upstream, git fetch upstream, then rebase or merge upstream/main into my branch to get current, and push to my fork. I keep my feature branches small and rebased on the latest upstream so PRs apply cleanly and reviewers see a focused diff. This model lets projects accept contributions from untrusted parties safely — maintainers review before anything merges. The discipline that keeps it smooth: sync with upstream frequently (don't let it drift for months), one focused branch per contribution, and never work directly on main in the fork.
Key Interview Signal: Cleanly separates origin/upstream and prevents drift with frequent syncing.
Q46. GitHub vs GitLab vs Bitbucket — comparison
Scenario: A company is standardizing its Git platform and wants a reasoned recommendation, not a fan-favorite.
Answer: All three do Git hosting, PRs/MRs, and CI, so I'd choose on fit, not popularity. GitHub has the largest ecosystem and community, Actions for CI, and is the default for open source and integrations. GitLab is the most all-in-one (built-in CI/CD, security scanning, issue tracking, self-hosting) — strong when you want one integrated DevOps platform and control over hosting. Bitbucket integrates tightly with the Atlassian stack (Jira, Confluence), which matters if a company already lives in those tools. My recommendation depends on their context: existing tooling and integrations, self-hosting/compliance needs, CI requirements, and cost at their seat count. I'd frame it as "what constraints do you have" rather than declaring an absolute winner — for an Atlassian shop, Bitbucket; for open source and breadth, GitHub; for an integrated self-hostable DevOps suite, GitLab.
Key Interview Signal: Recommends by constraints and existing ecosystem, not personal preference.
Section 3 — Advanced & Internals (Q47–Q57)
Q47. Git rerere — reuse recorded conflict resolution
Scenario: During a long-running rebase you keep resolving the same merge conflict over and over.
Answer: rerere ("reuse recorded resolution") makes Git remember how I resolved a specific conflict and auto-apply that resolution the next time the identical conflict appears. That's exactly the pain in the scenario — repeated rebases or a long-lived branch re-triggering the same conflict. I enable it with git config --global rerere.enabled true; then the first time I resolve, Git records it, and subsequent identical conflicts resolve automatically. It's especially valuable for maintaining a long-lived branch against a fast-moving main, or a rebase that keeps hitting the same spot. The caveat: it auto-applies a previous decision, so I still glance at the result to make sure the recorded resolution is still correct as code evolves. It's a quiet productivity multiplier that most people don't know exists.
Key Interview Signal: Knows rerere exists and uses it to kill repeated-conflict toil.
Q48. Git patch and format-patch — sharing changes
Scenario: You need to send a fix to someone who can't access your repo, or apply a change across two unrelated repositories.
Answer: Patches let me move changes as plain files without a shared remote. git diff > fix.patch (or git format-patch for commit-level patches that preserve author, message, and metadata) exports changes; the other side applies them with git apply (working-tree changes) or git am (recreates full commits from format-patch output). The classic uses: emailing a fix to a maintainer, air-gapped environments, or porting a specific change between repos that don't share history. format-patch/am is the better pair when I want to preserve authorship and commit history; plain diff/apply is fine for a quick unattributed change. It's an older workflow but still the right tool when there's no common remote — which is more common than people expect in secured or cross-org environments.
Key Interview Signal: Picks format-patch/am vs diff/apply based on whether authorship must be preserved.
Q49. Git internals — objects, SHA-1, packfiles
Scenario: An interviewer wants to know if you actually understand what Git is under the hood, not just the commands.
Answer: Git is a content-addressable object store. There are four object types: blobs (file contents), trees (directory listings pointing to blobs/trees), commits (a snapshot pointing to a tree plus parent commit(s), author, message), and tags. Every object is identified by a hash of its content (SHA-1, migrating to SHA-256), which is why identical content deduplicates automatically and why any change ripples the hashes upward — that's the basis of Git's integrity. Commits store snapshots, not diffs (diffs are computed on demand). Loose objects get compressed and combined into packfiles for storage and transfer efficiency. Understanding this explains a lot of behavior: why commit hashes change on rebase (content changed), why branches are cheap (just a pointer/ref to a commit), and why history rewriting is a big deal (every downstream hash changes). It's a Merkle-tree-like structure, which is also why tampering is detectable.
Key Interview Signal: Explains the content-addressed object model and derives real behavior from it.
Q50. Garbage collection — gc, prune, pack, dangling
Scenario: A repo's .git directory has grown huge and operations are slowing down.
Answer: Git accumulates loose objects over time — from commits, rebases, resets, deleted branches — and many become unreachable ("dangling"). git gc compresses loose objects into packfiles and cleans up, which shrinks .git and speeds operations; git prune removes unreachable objects past the grace period; git repack optimizes packfiles. Git runs gc automatically at thresholds, but on a big active repo I'd run git gc --aggressive during a maintenance window, or use git maintenance for scheduled background upkeep. The critical safety note: pruning permanently deletes unreachable objects, and that's the same pool reflog uses for recovery — so I never aggressively prune right after someone lost work, because I might destroy their only recovery path. So: gc/repack to reclaim space and speed, but treat prune with respect because it's the point of no return for lost commits.
Key Interview Signal: Reclaims space with gc/repack but respects that prune destroys recovery options.
Q51. Monorepo at scale — sparse checkout, partial clone
Scenario: A monorepo has grown so large that developers can't practically clone or check out the whole thing.
Answer: At scale you stop treating the repo as "clone everything." Partial clone (--filter=blob:none) skips downloading file contents until they're actually needed, so the initial clone is fast and blobs are fetched on demand. Sparse checkout populates only the directories a developer or CI job cares about, so nobody materializes the entire tree. Together they make a massive monorepo workable. For very large repos I'd also look at fsmonitor to speed up status, commit-graph for fast history traversal, and scheduled git maintenance. The requirements/trade-offs: this needs tooling discipline and a build system that understands the sparse layout, and some operations that assume full history/tree get more complex. But the payoff is that a 50GB monorepo behaves like a small one for day-to-day work. It's the same principle as CI checkout optimization, applied to humans.
Key Interview Signal: Combines partial clone + sparse checkout + maintenance to make scale tractable.
Q52. Git server administration — bare repos, hooks, access
Scenario: A team needs a self-hosted Git server for a repo that can't live on a cloud platform for compliance reasons.
Answer: A server-side repo is a bare repo — it has no working directory, just the Git database, because nobody edits files there directly; it exists to be pushed to and cloned from (git init --bare). Administration is really about three things: access control (SSH keys or a platform layer for authN/authZ, least privilege per user/team), server-side hooks (pre-receive/update/post-receive) to enforce policy that clients can't bypass — reject unsigned commits, enforce naming, trigger deploys — and reliability (backups of the bare repo, since it may be the canonical copy; monitoring; and gc/maintenance so it stays fast). For most teams I'd run a self-hosted platform (GitLab, Gitea) rather than raw bare repos, to get UI, permissions, and CI, but the underlying model is still bare repos plus server hooks. The compliance angle is exactly why server-side enforcement matters — it can't be skipped locally.
Key Interview Signal: Understands bare repos + server-side hooks as unbypassable enforcement, plus backups.
Q53. Platform migration — GitHub↔GitLab, SVN to Git
Scenario: The company is moving from an old SVN server (or from GitHub to self-hosted GitLab) and can't afford to lose history or disrupt developers.
Answer: I treat a migration as a project, not a command. First, clarify requirements: preserve full history and authorship? migrate issues/PRs/CI too, or just code? acceptable downtime? For SVN→Git I'd use git svn or svn2git with an authors mapping so commits attribute to real people, and I'd expect to clean up branches/tags that don't map cleanly. For GitHub↔GitLab, the code+history migrates easily via mirroring, but the metadata (PRs, issues, CI config, protections, hooks) is the hard part and needs platform import tools or scripting. My plan: run a trial migration to a staging instance, validate history/authorship/counts against the source, migrate CI and branch protections deliberately, then do a cutover with a freeze window and a rollback plan (keep the old system read-only, not deleted, until verified). Communicate the freeze and new URLs clearly. The risk isn't the code — it's the metadata and the human cutover.
Key Interview Signal: Treats migration as a validated, staged project with a rollback and clear cutover.
Q54. Git and compliance — audit trails, traceability
Scenario: An auditor asks you to prove that every production change was reviewed, authorized, and traceable to a person and a ticket.
Answer: Git is naturally a strong audit trail — immutable, hash-linked history of who changed what and when — but I have to configure the process to make it provably controlled. The controls I'd point to: branch protection requiring PR review and passing checks (proves changes were reviewed), signed commits/tags (proves authorship, not just a self-reported name), protected history (no force-push, so the record can't be quietly altered), and commit conventions linking each change to a ticket ID for traceability from requirement→code→deploy. Deploying strictly from tags ties the running artifact back to an exact, reviewed, signed commit. For the auditor, the story is end-to-end: a change is proposed, reviewed by authorized owners, signed, merged through an enforced gate, tagged, and deployed — every step recorded and tamper-evident. The gap I'd check for is anything that bypasses the gate (admin overrides, direct pushes), because compliance is only as strong as its weakest bypass.
Key Interview Signal: Maps Git controls to audit requirements and hunts for the bypass that breaks them.
Q55. Rewriting history — filter-repo, BFG
Scenario: A 2GB binary (or a secret) was committed years ago and is still bloating every clone; it must be removed from all history.
Answer: Removing something from all history means rewriting every commit after it, which changes hashes across the repo — a serious, coordinated operation. git filter-repo is the modern, fast, recommended tool (BFG is a simpler alternative focused on stripping big files/secrets); the old filter-branch is slow and error-prone, so I avoid it. The process: rewrite history to purge the file/secret, force-push all branches and tags, then everyone must re-clone or hard-reset because their old history no longer matches. The critical prerequisites: coordinate a freeze so nobody's mid-work, back up the repo first, and — if it was a secret — rotate the secret regardless, because it's already been exposed in every prior clone and can't be un-leaked by rewriting. After the rewrite, run gc to actually reclaim the space. This is a "measure twice, cut once" operation; I never do it casually on a shared repo.
Key Interview Signal: Uses filter-repo/BFG, coordinates the disruptive force-push, and rotates leaked secrets anyway.
Q56. Performance at scale — fsmonitor, maintenance
Scenario: On a huge repo, everyday commands like git status take many seconds and developers are frustrated.
Answer: Slow git status on a large repo is usually Git scanning the entire working tree. core.fsmonitor fixes the biggest one — it uses the OS filesystem watcher to know which files changed, so status only checks those instead of the whole tree, turning multi-second status into near-instant. Beyond that, git maintenance start schedules background upkeep: writing/updating the commit-graph (fast history traversal for log/merge-base), incremental repacking, prefetching, and loose-object cleanup. The untracked cache and sparse checkout help too. My approach is evidence-based: measure where the time actually goes (GIT_TRACE/timing) rather than guessing, enable fsmonitor and commit-graph, schedule maintenance, and re-measure. For truly massive repos this is the difference between Git being usable or not. The trade-off is a bit of setup and background resource use, which is well worth it at scale.
Key Interview Signal: Targets fsmonitor + commit-graph + maintenance and measures before/after.
Q57. GitOps — Git as source of truth for infra
Scenario: Infra changes are being made by hand in a cloud console, drift is rampant, and nobody knows the true state of the environment.
Answer: GitOps makes Git the single source of truth for infrastructure and deployments: the desired state (Kubernetes manifests, Terraform, Helm) lives in Git, and an agent (Argo CD, Flux) continuously reconciles the live environment to match the repo. That directly kills the scenario's problems — no more manual console changes, because the repo is the state; drift is detected and either auto-corrected or flagged; and every change is a reviewed, auditable PR with a clear author and rollback (just revert the commit). The benefits I'd emphasize: full auditability, easy rollback, consistency across environments, and self-healing reconciliation. The requirements and trade-offs: you need discipline that all changes go through Git (lock down direct console access, or drift returns), good secret handling (sealed secrets/vault, never plaintext in the repo), and the team has to adopt a "change infra = open a PR" culture. Done right, your environment's state is reviewable, reproducible, and recoverable — infra treated with the same rigor as application code.
Key Interview Signal: Frames Git as the reconciled source of truth and locks down the drift path (direct console access).
Section 4 — Incident & Troubleshooting Scenarios (Q58–Q80)
These are handled with the incident mindset: What changed? Who's affected? What's confirmed healthy? What evidence do we have? What are we assuming? What's the safest next action? How do we communicate and prevent recurrence?
Q58. Merge conflict in critical production hotfix
Scenario: Prod is down, you have the fix, but merging the hotfix into the release branch throws conflicts and the clock is ticking.
Answer: Under pressure I slow down for ten seconds to avoid a bad merge that makes it worse. What changed / who's affected: confirm the outage scope and that this hotfix is genuinely the fix. Safest next action: branch the hotfix directly off the current production commit (the actual deployed tag), not off a diverged develop, so I'm conflicting against reality. I open the conflicts, resolve only the minimal lines needed for the fix, and — critically — I understand each conflict rather than blindly picking "ours/theirs," because a wrong resolution during an outage is a second incident. I test the resolved build, even a quick smoke test, before deploying. Communication: I post status the whole time — "fix identified, resolving merge conflict, ETA X" — so stakeholders aren't guessing. Prevent recurrence: big conflicts on a hotfix usually mean the release branch drifted far from prod; afterward I'd shorten branch lifetimes / adopt trunk-based so hotfixes apply cleanly. Deploy from a clean, tested state, never a rushed guess.
Key Interview Signal: Stays calm, branches off real prod, resolves minimally with understanding, and communicates continuously.
Q59. Accidentally committed secrets to repo
Scenario: A developer notices an AWS key was committed and pushed an hour ago.
Answer: My first assumption is the worst case: it's already leaked — pushed means it may have been cloned, cached, or scraped within minutes. So step one is rotate/revoke the key immediately; everything else is secondary. Removing the commit does not make it safe, because it lives in history and in every existing clone. Evidence: check if the key was actually used maliciously (CloudTrail/access logs) to scope any real damage, and check whether it hit any external mirrors. Next actions: after rotation, scrub it from history with git filter-repo/BFG and force-push in a coordinated way, then have everyone re-clone. Communicate: tell security and the team promptly — hiding a leak is worse than the leak. Prevent recurrence: add pre-commit secret scanning plus an enforced CI/push-protection gate, and move secrets into a vault injected at runtime so there's nothing to commit. The order matters: rotate → assess → scrub → prevent. Never rotate last.
Key Interview Signal: Assumes compromise and rotates first; treats history scrubbing as secondary, not the fix.
Q60. Force push overwrote teammate's commits
Scenario: After a force-push to a shared branch, a teammate reports their recent commits are gone.
Answer: Don't panic — this is almost always recoverable. Evidence first: the overwritten commits still exist as unreachable objects. If the teammate has them locally, their git reflog or branch tip has the commits directly — easiest recovery. If not, the server's reflog (or the CI's last checkout, or any other clone) has the pre-force-push hash. Safest next action: identify the lost commit hash from reflog, create a branch from it, and merge those commits back in properly — I do not do another force-push while I'm still unsure, because that risks compounding the loss. I re-verify nothing else was dropped. Communicate: tell the teammate exactly what happened and that it's recoverable, and coordinate so we don't both push simultaneously. Prevent recurrence: this shouldn't be possible on a shared branch — enable branch protection to block force-pushes, and where people need to update a shared branch use --force-with-lease (which refuses if someone else pushed) instead of raw --force. The root cause is a policy gap, not just a mistake.
Key Interview Signal: Recovers via reflog, avoids compounding, and closes the gap with branch protection + force-with-lease.
Q61. Detached HEAD — commits lost
Scenario: A developer worked in detached HEAD, committed, switched branches, and their commits appear gone.
Answer: Reassure first — the commits aren't deleted, just unreferenced. In detached HEAD, commits have no branch pointing to them, so switching away leaves them dangling but intact. Evidence: git reflog lists recent HEAD positions including those detached commits. Safest next action: find the hash and git branch <name> <hash> to anchor the work to a real branch — done, recovered, no data lost. What I avoid: running gc, reset, or anything destructive before recovering, since that's the only thing that could actually lose them. Communicate: walk the developer through reflog so they learn the recovery, not just receive it. Prevent recurrence: the habit is to create a branch before experimenting on a raw commit (git switch -c when checking out a hash you'll commit on), and I'd mention that Git even warns you on detached checkout — the fix is teaching people to heed it. Calm, reflog, branch — that's the whole play.
Key Interview Signal: Recognizes commits are dangling-not-gone, recovers with reflog, and teaches the prevention habit.
Q62. git reset --hard lost uncommitted work
Scenario: Someone ran git reset --hard and lost changes that were never committed.
Answer: I'm honest about the hard part here: reflog only tracks committed history, so truly uncommitted work — changes that were never staged or committed — is generally not recoverable by Git. So I set expectations immediately rather than giving false hope. Evidence / what to check: if anything was ever staged (even briefly), there may be a dangling blob recoverable via git fsck --lost-found, so that's worth trying. IDE local history, editor backups, or OS file-versioning (Time Machine, etc.) are often the real rescue for uncommitted work. Communicate: tell them clearly what's recoverable and what likely isn't, and check those other sources before concluding. Prevent recurrence: this is the strongest argument for commit early and often — a quick WIP commit or git stash before any risky operation would have made this a non-event, and reset --hard should be treated as a loaded gun (confirm the tree is clean, or stash, before firing). I'd also suggest aliasing away casual --hard use. The lesson is workflow, not a magic recovery.
Key Interview Signal: Honest about the limits of recovery, checks fsck/IDE history, and drives home commit-early prevention.
Q63. Merge produced broken build — 3 days unnoticed
Scenario: A merge broke main's build, but nobody noticed for three days, and lots of work has landed since.
Answer: What changed: first I find the exact breaking merge — git log --oneline --merges around the timeframe, and git bisect if it's not obvious which change broke it. Who's affected / what's healthy: three days of commits are now stacked on top, so I can't just reset — that would nuke everyone's later work. Safest next action: git revert the specific breaking merge (with -m 1 for the mainline parent) to restore a working build without rewriting the shared history everyone's built on; then reapply the intended change correctly. Evidence: why did it go unnoticed for three days? That's the real incident. Communicate: notify the team that main was broken and is being restored, and that later work is safe. Prevent recurrence — this is the core issue: the build broke on main and nothing alerted anyone. I'd make CI on main a required, monitored signal with alerting, so a broken main pages someone in minutes, not days, and require passing checks before merge so it can't break in the first place. A three-day-silent break is a monitoring failure as much as a merge failure.
Key Interview Signal: Reverts safely on shared history and identifies the real root cause: missing CI alerting on main.
Q64. Repo is 10GB — clone takes 30 minutes
Scenario: New hires and CI both waste huge time cloning a 10GB repo.
Answer: Evidence first — why is it 10GB? I'd inspect with git count-objects -vH and look for the biggest objects in history (git rev-list --objects piped to size analysis, or a tool like git-sizer). The answer is almost always large binaries committed into history. What's affected: everyone cloning — humans and CI. Immediate mitigations (no history rewrite): for CI, shallow clone (--depth 1) and partial clone (--filter=blob:none) cut clone time dramatically without touching the repo; for humans, partial + sparse checkout. That solves the pain quickly. Real fix: if the bloat is old binaries, migrate them to Git LFS and rewrite history with filter-repo/lfs migrate to actually shrink the 10GB — a coordinated, disruptive operation requiring a freeze, backup, force-push, and re-clone by everyone. Communicate & sequence: ship the quick clone-time wins first (they need no coordination), then plan the history rewrite as a scheduled event. Prevent recurrence: a pre-commit/CI check that blocks large binaries and enforces LFS, so the repo can't re-bloat. Quick relief now, structural fix on a schedule.
Key Interview Signal: Diagnoses the bloat source, ships no-coordination wins first, and plans the disruptive rewrite deliberately.
Q65. Rebase conflict hell — 50 conflicts
Scenario: Rebasing a long-lived feature branch onto main produces 50 conflicts and it feels hopeless.
Answer: Assess before grinding: 50 conflicts usually means the branch drifted from main for far too long. Safest next action / options: I don't have to power through blindly — git rebase --abort returns me to safety with zero risk, and I can choose a better path. Sometimes a single merge is far less painful than a rebase here, because a rebase re-applies each commit and re-hits conflicts commit-by-commit, whereas a merge resolves the combined diff once. If I do rebase: enable rerere so I never resolve the same conflict twice, and go slowly, understanding each resolution rather than mashing "theirs." Evidence: I test frequently, not just at the end. Communicate: if it's blocking others, I say so and give an ETA or a plan B. Prevent recurrence — the real lesson: long-lived branches cause this. I'd rebase/merge from main frequently (daily), keep branches short-lived, and move toward trunk-based development so a branch never drifts 50 conflicts away. The fix isn't heroics on this rebase; it's never being here again.
Key Interview Signal: Knows --abort is free, weighs merge-vs-rebase, uses rerere, and targets long-lived branches as the cause.
Q66. Wrong commit on main — revert vs reset
Scenario: A wrong commit landed on main; the team debates revert vs reset.
Answer: The deciding question is has main been shared/pulled since the bad commit — and on main, the answer is essentially always yes. Therefore: revert, not reset. git revert <hash> creates a new commit that undoes the change, leaving everyone's history intact and providing an auditable record of the undo. reset rewrites history, which on a shared branch breaks every other clone and forces messy recovery — unacceptable on main. The only time reset is okay is a commit that's still purely local and never pushed. Safest next action: revert, verify the build, push. If the bad commit exposed a secret or bad data, reverting the code isn't enough — I'd also handle that side effect (rotate secret, fix data). Communicate: note the revert so people understand the extra commit. Prevent recurrence: how did a wrong commit reach main directly? Tighten branch protection so changes go through reviewed PRs with passing checks, making a stray wrong commit on main much harder in the first place. Public history → revert, always.
Key Interview Signal: Chooses revert for shared history on principle and questions how the commit bypassed review.
Q67. Git bisect finds bug 200 commits ago
Scenario: A subtle bug was introduced ~200 commits back and manual search is hopeless.
Answer: This is exactly what bisect is for — binary search collapses 200 commits into ~8 tests. Prerequisite (the part people skip): I need a reliable, reproducible test for the bug, because bisect is only as trustworthy as the check I run at each step — a flaky repro gives a wrong culprit. Process: git bisect start, mark a known-good old commit and the bad current one, and Git checks out midpoints; I test each and mark good/bad. If I can script the test, git bisect run <script> automates the whole thing and finds the culprit hands-free. Evidence: once bisect names the commit, I read that diff to understand the actual root cause, not just the commit. Communicate: share the offending commit and the fix plan. Prevent recurrence: a bug hiding for 200 commits means it slipped past testing — I'd add a regression test for it (so it can never silently return) and look at whether coverage/CI should have caught it earlier. Bisect finds it fast; the regression test keeps it dead.
Key Interview Signal: Uses bisect run with a reliable repro and adds a regression test so the bug can't recur silently.
Q68. Submodule stuck on wrong commit
Scenario: A submodule keeps checking out an old/wrong commit and builds fail inconsistently across the team.
Answer: Root cause first: the parent repo pins the submodule to a specific commit, so "stuck on wrong commit" means the parent's recorded pointer is stale, or people haven't run submodule update. Evidence: git submodule status shows the exact commit each submodule is on and whether it's out of sync (a + or - prefix tells me). Safest next action: inside the submodule, checkout the correct commit/branch, then — the step people forget — go to the parent repo and commit the updated submodule pointer, so the correct commit is what everyone gets. For teammates on stale checkouts, git submodule update --init --recursive syncs them to the parent's recorded pointer. Communicate: tell the team to run the recursive update, since a plain pull won't move submodules. Prevent recurrence: submodules cause this constantly — I'd add submodule update --recursive to the build/CI bootstrap so it can't be forgotten, and honestly evaluate whether a package manager or subtree would remove this whole class of problem for the team. The "two-step pointer" nature of submodules is the real culprit.
Key Interview Signal: Diagnoses the stale pointer, commits the pointer in the parent, and automates the recursive update.
Q69. CI fails only on main — works on feature branches
Scenario: Tests pass on every feature branch but fail on main after merge.
Answer: What changed between the two contexts? This pattern almost always means the combination of merged changes breaks, not any single branch — each feature was tested in isolation against an older main, so nobody tested them together. Evidence to gather: is main's CI using different config, secrets, environment, or a full test suite that PRs skip? Is there a caching or ordering difference? I'd compare the main pipeline definition and environment against the branch pipeline directly. Common culprits: semantic merge conflicts (two changes that merge cleanly but interact badly), tests that depend on shared state/order, or main running integration/e2e tests that branches don't. Safest next action: reproduce main's exact CI conditions locally or in a branch, then bisect the merges if needed. Communicate: flag that main is red and being investigated. Prevent recurrence — the key fix: require branches to be up-to-date with main before merge (merge queue / "require branches up to date"), so features are tested together with the latest main before landing, catching the interaction pre-merge. Run the same test suite on PRs that runs on main — no gap where main tests things branches don't.
Key Interview Signal: Recognizes it's an integration/interaction problem and fixes it with a merge queue + parity between PR and main CI.
Q70. Committed to wrong branch — move commits
Scenario: You made several commits on main (or develop) that were meant for a feature branch.
Answer: Assess: have the commits been pushed/shared? That decides how careful I have to be. If still local (common case): create the feature branch at the current point (git branch feature), then move the wrong branch's pointer back to before those commits (git reset --hard origin/main or the pre-commit hash) — now the commits live only on feature, cleanly moved. Alternatively, cherry-pick the specific commits onto the correct branch, then reset the wrong branch. If already pushed to a shared branch: I do not reset shared history — instead I revert the misplaced commits on the shared branch (to undo them safely) and re-apply them via cherry-pick on the correct branch. Evidence/verify: confirm the commits now exist only where intended and the wrong branch is clean. Communicate: if it was on a shared branch, note the revert. Prevent recurrence: the habit is git status/branch-name awareness before committing — I'd suggest a prompt that shows the current branch, and small focused commits so a slip is easy to move. Local slip = reset+rebranch; shared slip = revert+cherry-pick.
Key Interview Signal: Branches on pushed-vs-local, uses reset locally but revert on shared branches, and adds branch-awareness prevention.
Q71. Deleted branch with unmerged work — recovery
Scenario: A branch with unmerged commits was deleted and the work seems gone.
Answer: Reassure — deleting a branch only removes the pointer, not the commits, which linger as unreachable objects until gc. Evidence: git reflog (and git fsck --lost-found for dangling commits) will surface the tip commit of the deleted branch — the deletion itself is usually logged. Safest next action: find the branch's last commit hash and recreate it: git branch <name> <hash> — fully recovered. If it was a remote branch someone else deleted and I don't have it locally, I check other clones, the server reflog, or a recent CI checkout that fetched it. What I avoid: running gc/prune before recovering, since that's what would actually destroy the dangling commits. Communicate: confirm to the owner it's back and how. Prevent recurrence: enable "require branches up to date"/protection and, on platforms, deleted-branch restore; more importantly, don't delete branches with -D (force) without checking merged status — git branch -d refuses to delete unmerged branches for exactly this reason. Reflog/fsck almost always saves the day.
Key Interview Signal: Recovers via reflog/fsck, avoids gc, and prevents with -d (safe delete) over -D.
Q72. 200 tiny commits — clean before merge
Scenario: A feature branch has 200 messy "wip", "fix typo", "try again" commits and needs to land cleanly.
Answer: Goal: main should read as meaningful changes, not someone's keystroke-by-keystroke diary. Two clean options: interactive rebase (git rebase -i) to squash/fixup/reword the 200 into a handful of logical commits — full control, best when some intermediate commits are worth keeping — or simply squash-merge the PR, collapsing all 200 into one clean commit on main, which is the fastest path and makes rollback trivial. For 200 pure-noise commits, squash-merge is usually the pragmatic choice. Safety: rewriting history is fine here because it's my unshared feature branch — I'd never do this to shared history. I verify the squashed result builds and matches the branch's final state. Communicate: none really needed beyond the PR. Prevent recurrence: this is normal WIP flow, so I don't over-correct — the point is just to clean at the boundary (before merge), not to demand tidy commits mid-development. I'd set the repo's default merge method to squash so it happens automatically, and encourage --fixup/autosquash for people who want curated history. Messy locally is fine; clean on main is the rule.
Key Interview Signal: Uses squash/interactive-rebase to clean at the merge boundary and automates it via default squash-merge.
Q73. Two devs rebased same branch — duplicates
Scenario: Two developers independently rebased the same shared branch and now history shows duplicated commits and divergence.
Answer: Root cause: this is the golden-rule violation — rebasing shared history. Rebasing rewrites hashes, so when two people rebase the same branch independently, they create two different versions of "the same" commits, and reconciling them produces duplicates and conflicts. Assess / evidence: git log --graph on both copies to see exactly where they diverged and which commits are genuine duplicates vs distinct work. Safest next action: pick one canonical version as the source of truth, and carefully reconcile — often the cleanest recovery is to identify the unique commits from each side and cherry-pick the truly-missing ones onto the chosen base, dropping the duplicates. rerere helps if the same conflicts recur. I do this deliberately, verifying no real work is dropped. Communicate: coordinate so both devs stop pushing to the branch until it's reconciled, and agree who fixes it. Prevent recurrence — the actual fix: never rebase shared branches. Enforce it: branch protection against force-push, team convention that shared branches are only ever merged (not rebased), and rebase only local unshared work. The mess is a policy failure, and the policy is the golden rule.
Key Interview Signal: Names the golden-rule violation, reconciles by cherry-picking unique commits, and enforces no-rebase-on-shared.
Q74. Hotfix needed but main has unreleased features
Scenario: Prod needs an urgent fix, but main contains unreleased, not-yet-tested features you can't ship.
Answer: Constraint: I must ship only the fix, not the unreleased work sitting on main. Safest next action: branch the hotfix off the exact commit that's currently in production — that's the released tag, not the tip of main — so the fix is isolated from the unreleased features. Apply the minimal fix there, test it, tag it, and deploy that. This is the core reason release tags/branches exist: they let me patch what's live without dragging along in-flight work. Then reconcile: merge or cherry-pick the fix back into main so the fix isn't lost when the next release goes out (and resolve any small conflict there). Evidence: confirm the hotfix branch really is based on the deployed version, not a diverged one. Communicate: status updates during the outage, and note the hotfix tag. Prevent recurrence: this is a branching-strategy signal — if main routinely holds a pile of unreleased, unshippable work, hotfixes are always painful. Trunk-based development with feature flags (unfinished features merged but disabled) means main is always releasable, so a hotfix is just a normal small change. The tag-based hotfix is the immediate answer; releasable-main is the structural one.
Key Interview Signal: Branches off the deployed tag to isolate the fix, back-merges it, and points to releasable-main (flags) as prevention.
Q75. .gitignore not working — file already tracked
Scenario: A file is listed in .gitignore but still shows up in every commit and diff.
Answer: This is the classic gotcha, and the diagnosis is immediate: .gitignore only affects untracked files. If the file was ever committed, Git keeps tracking it regardless of ignore rules — the ignore is silently ineffective for it. Evidence: git ls-files <file> confirms it's tracked; git check-ignore -v <file> confirms the ignore rule matches but isn't applying because it's tracked. Safest next action: stop tracking it while keeping it on disk: git rm --cached <file> (add -r for a directory), then commit. Now the ignore rule takes effect going forward. If it was committed with real content that shouldn't be in the repo (a secret, a huge binary), removing it from current tracking isn't enough — it's still in history, so I'd handle that separately (rotate secret / rewrite history). Communicate: if others tracked it too, coordinate the removal so it doesn't get re-added. Prevent recurrence: add proper ignore patterns before creating those files (scaffold .gitignore at project start), and I'd double-check no build artifacts or env files slip in via a pre-commit check. rm --cached is the one-line fix people always miss.
Key Interview Signal: Instantly IDs the already-tracked gotcha, fixes with rm --cached, and flags the history/secret follow-up.
Q76. Reverted merge commit, can't re-merge branch
Scenario: A feature branch was merged, the merge was reverted, and now re-merging the fixed branch brings in nothing — Git thinks it's already merged.
Answer: This is a well-known, counterintuitive trap, so I'd explain the why clearly: reverting a merge commit undoes the merge's changes but Git still records that those commits were merged. So when you merge the branch again, Git sees the commits are already in history and brings in nothing new — even though the reverted change removed their effect. Safest next action — two clean options: (1) revert the revert (git revert <revert-commit>) to restore the changes, then continue adding new work on top; or (2) rebase the feature branch to create new commits (new hashes) that Git doesn't consider already-merged, then merge those. Which one depends on whether the original commits were fine (revert-the-revert) or need reworking (rebase into fresh commits). Evidence: git log/--graph to see the merge, the revert, and confirm the "already merged" state. Communicate: explain to the team why the naive re-merge did nothing, since it confuses everyone the first time. Prevent recurrence: be cautious reverting merge commits specifically — prefer reverting the individual problematic commits, or reset if it's still local/unshared, so you don't poison future merges of that branch.
Key Interview Signal: Explains the revert-a-merge trap and resolves it with revert-the-revert or a fresh-hash rebase.
Q77. Large binary committed — repo bloated permanently
Scenario: A multi-GB binary was committed long ago; deleting it in a later commit didn't shrink the repo at all.
Answer: Why deletion didn't help: Git stores every version in history, so a later "delete" commit just adds a new state — the huge blob still lives in history and in every clone, permanently inflating the repo. Removing it for real means rewriting history. Evidence: git count-objects -vH / git-sizer to confirm the bloat and locate the offending object. Safest next action: use git filter-repo (or BFG for the big-file case) to purge that blob from all commits, then git gc to actually reclaim the space. Because this rewrites every subsequent commit's hash, it's disruptive: I coordinate a freeze, back up the repo first, force-push all branches/tags, and have everyone re-clone. If the file is a legitimately-needed large asset, the right home is Git LFS, so I'd migrate it there rather than just deleting. Communicate: schedule the rewrite as an announced event with a clear re-clone instruction. Prevent recurrence: add a pre-commit/CI check that blocks large binaries and enforces LFS tracking, so a big file can never be committed raw again. The key insight I'd stress: in Git, deleting a big file doesn't remove it from history — only a history rewrite does.
Key Interview Signal: Explains why deletion fails, rewrites history with filter-repo/BFG + gc, migrates to LFS, and blocks recurrence.
Q78. Git pull creates unexpected merge commits
Scenario: Every git pull on a shared branch adds noisy "Merge branch X into X" commits, cluttering history.
Answer: Root cause: git pull defaults to fetch + merge. When my local branch and the remote have both advanced, that merge creates a merge commit — and if this happens on every pull, history fills with meaningless merge bubbles. Safest next action / fix: use git pull --rebase, which replays my local commits on top of the fetched remote commits instead of merging, giving clean linear history with no bubble. To make it permanent I set git config --global pull.rebase true (or pull.ff only to force fast-forward and error out when a real merge would be needed, prompting a deliberate choice). Evidence: git log --graph shows the bubbles clearly, and confirms the fix produces linear history. Nuance I'd mention: rebasing during pull only rewrites my local, unpushed commits, so it's safe — it doesn't touch shared history. If conflicts arise, that's just my local work meeting the remote's; I resolve and continue. Communicate: I'd share the config as a team default so everyone's history stays clean, not just mine. Prevent recurrence: set the pull.rebase/ff-only default org-wide (config or template) so nobody has to remember. It's a config problem with a config fix.
Key Interview Signal: IDs pull's default-merge behavior and fixes it permanently with pull.rebase/ff-only as a team default.
Q79. Feature branch 3 months old, 500 behind main
Scenario: A feature branch hasn't been synced in three months and is 500 commits behind main; integrating it looks terrifying.
Answer: Assess honestly first: 500 commits of drift means integration will be painful no matter what, and I decide the approach based on the branch's state, not bravado. Options: a straight rebase re-applies my commits over 500 commits of change and may hit conflicts repeatedly commit-by-commit; a single merge from main resolves the combined diff in one pass, which is often less brutal at this scale. Sometimes the honest answer is that three months of divergence is better handled by re-doing the feature in smaller pieces on top of current main, if the old branch is largely stale. Tooling: enable rerere so repeated conflicts resolve once, integrate incrementally, and test frequently rather than only at the end. Evidence: git log --graph/diff to understand the scope before committing to a path; consider --abort freely if a chosen path goes badly — it's free. Communicate: flag that this is a large, risky integration and give a realistic timeline. Prevent recurrence — the real lesson: branches should never drift three months. Sync from main daily, keep branches short-lived, and move toward trunk-based development so a branch is measured in days, not months. This incident is a process failure; the fix is short-lived branches so it's structurally impossible.
Key Interview Signal: Weighs merge/rebase/redo pragmatically at scale, uses rerere/incremental testing, and targets short-lived branches as the cure.
Q80. Same conflict appearing repeatedly
Scenario: The same merge conflict keeps reappearing on every rebase/merge, and resolving it repeatedly is exhausting.
Answer: Immediate relief: enable git config --global rerere.enabled true — "reuse recorded resolution." Git records how I resolve a conflict and auto-applies that same resolution when the identical conflict recurs, so I resolve once and Git handles the repeats. That directly kills the exhausting re-resolution loop. But I'd dig into why the same conflict recurs, because rerere treats the symptom. Common root causes: a long-lived branch repeatedly rebased against a fast-moving main (same overlapping lines keep colliding), two branches persistently editing the same hotspot, or repeated cherry-picks/merges of related changes. Evidence: look at which file/lines conflict every time — a single recurring hotspot often signals a design issue (a file too many people touch) or a workflow issue (branch living too long). Safest actions: rerere for the toil, plus address the cause — shorten branch lifetimes / integrate more often so the branch doesn't keep re-colliding, or refactor the contested hotspot so fewer changes overlap. Communicate: if two people keep conflicting on the same code, coordinate ownership. Prevent recurrence: turn on rerere by default team-wide, and treat a chronically-conflicting file as a signal to restructure it or the workflow around it. Rerere for the pain, root-cause for the cure.
Key Interview Signal: Reaches for rerere immediately but investigates why the conflict recurs and fixes the workflow/hotspot cause.
How to use this guide
Each answer models the same reasoning loop an interviewer wants to hear: clarify the problem, avoid premature assumptions, gather evidence, act in the safest order, communicate status, and prevent recurrence. For incident questions, walk the 7-step framework out loud. For tool/design questions, give the concept briefly, then real production usage, failure modes, and what you'd automate or check. The goal isn't to recite commands — it's to demonstrate that you eliminate uncertainty faster and leave systems safer than you found them.