skip to content

In Git, how do you use git bisect to find the commit that introduced a bug?

level: juniorimportance: should knowfreq 50%

answer

  1. halving, not scanning
  2. needs two endpoints
  3. one answer per checkout
  4. start, bad, good, reset
  5. prints the first bad commit

basics

~10 s

git bisect binary-searches history. Run git bisect start, mark a broken commit bad and an older working one good, test each commit Git checks out and answer good or bad, then git bisect reset.

solid answer

~40 s

`git bisect` runs a binary search over the commit graph. You begin with `git bisect start`, mark a commit where the bug is present with `git bisect bad`, and an older commit known to work with `git bisect good` — or give both at once as `git bisect start <bad> <good>`. Git then checks out a commit about halfway between them on a detached HEAD and prints how many revisions remain. You build and test it, then answer `git bisect good` or `git bisect bad`; each answer halves the range, so N commits need about log2(N) tests. When one candidate is left Git prints `<sha> is the first bad commit` with that commit's details. `git bisect reset` ends the session and returns you to the branch you started from.

code

console · 10 lines
console
$ git bisect start HEAD v1.4.0
Bisecting: 12 revisions left to test after this (roughly 4 steps)
[9fceb02...] refactor parser
$ make && ./run-check
$ git bisect bad
Bisecting: 6 revisions left to test after this (roughly 3 steps)
$ git bisect good
...
9fceb02d8d5 is the first bad commit
$ git bisect reset

go deeper

for a junior

Be ready to name the four commands in order: start, bad, good, reset, and to say that bisect is a binary search over commits. Mention that you must build and test each commit Git checks out.

for a middle

Explain the mechanics: two endpoints define the candidate set, each answer halves it, about log2(N) steps, detached HEAD at each candidate, and the first bad commit is a boundary rather than proof of guilt.

for a senior

Show how you make bisect reliable in practice: a clean tree, a scripted reproduction of one stable symptom, path-limited searches, and recovery through bisect log and replay when an answer was wrong.

for a principal

Frame bisect as the payoff for history hygiene and a fast, deterministic test signal. Argue what the team must invest — buildable commits, a quick reproducer — for mean time to diagnosis to drop.

## What bisect is for You know something worked in the past and is broken now, but not which of hundreds of commits broke it. Reading every diff is linear work. `git bisect` turns the hunt into a binary search over history: each test halves the suspects, so about log2(N) tests isolate the culprit among N commits. A thousand commits become roughly ten tests. ## Marking the endpoints A bisect needs two anchors: a commit where the symptom is present (`bad`) and an ancestor where it is absent (`good`). Open the session with `git bisect start`, then: - `git bisect bad` marks the checked-out commit as broken; `git bisect bad <rev>` marks a named one. - `git bisect good <rev>` marks a known-working ancestor. You may pass several good commits. - `git bisect start <bad> <good>` does both in one line. Once Git has one bad and at least one good commit it computes the set of commits reachable from bad but not from good, picks one near the middle, and checks it out. ## The test loop Each step Git prints something like `Bisecting: 12 revisions left to test after this (roughly 4 steps)` and leaves you on a detached HEAD at the candidate commit — you are not on a branch, so do not commit there. Build, reproduce (or fail to reproduce) the symptom, and answer `git bisect good` or `git bisect bad`. Answer about the same symptom every time; changing what you test mid-search produces a meaningless result. Git records answers under `refs/bisect/*` and in `.git/BISECT_LOG`, so the state survives shells. ## Reading the result When the range collapses to a single commit, Git prints `<sha> is the first bad commit` followed by the author, date, message and changed files. First bad means the earliest commit, in the searched range, from which the symptom is present — the boundary between good and bad, not necessarily the commit whose diff looks guilty. It may merely expose a latent defect introduced earlier. ## Ending the session `git bisect reset` deletes the bisect refs and checks out the branch you were on when you started; `git bisect reset <commit>` lands you somewhere else instead. Leaving a session open is the classic beginner trap — you stay on a detached HEAD and later wonder where your work went. `git checkout` on its own does not clear bisect state. ## Session tools `git bisect log` prints the answers so far; redirect it to a file and `git bisect replay <file>` re-runs them, which lets you undo a mistaken answer by editing the log. `git bisect visualize` (alias `view`) shows the remaining candidates. `git bisect start -- <path>` restricts the search to commits touching given paths, which can cut the candidate set sharply when you know the subsystem. ## Preconditions Bisect assumes a clean working tree (stash or commit first) and that every candidate commit can actually be built and tested; commits that cannot be judged are handled with `git bisect skip`. It also assumes monotonicity: the symptom appears once and stays. An intermittent, flaky symptom makes answers unreliable, and one wrong answer sends the whole search into the wrong half.

  • Can you bisect for something other than a bug, such as when a fix or a slowdown landed?
    Yes — good and bad are only a naming convention for old state versus new state. `git bisect old` and `git bisect new` work out of the box, and `git bisect start --term-old=<term> --term-new=<term>` lets you name them, for example fast and slow for a performance change. `git bisect terms` prints the terms currently in use, and the final message uses your term.
  • What does the first bad commit actually prove?
    Only that the symptom is absent at its parent and present at it, within the range you searched. That is usually the offending change, but it can also be a commit that merely exposes an older latent bug, or one that enables a code path. Read the diff before assigning blame, and verify by testing its parent directly.
  • What happens if you answer one step wrongly?
    Git discards the wrong half and converges on an innocent commit. Because answers are recorded you can recover: save `git bisect log` to a file, correct the wrong entry, run `git bisect reset`, then `git bisect replay <file>` to rebuild the session from the corrected answers rather than starting over.

Like finding a page in a sorted book: open the middle, decide which half the page is in, and repeat — ten opens cover a thousand pages.

saying these in an interview costs you the question

  • Thinks bisect finds bugs by itself without you testing
  • Forgets git bisect reset and keeps working on a detached HEAD
  • Marks the newest commit good and the old one bad
  • Tests a different symptom at each step
  • Assumes the first bad commit is always the guilty diff

context