Learning Objectives
By the end of this lesson you will be able to:
- Explain why binary search turns hundreds of commits into a handful of tests
- Identify a good known-good and known-bad boundary
- Recognise the three prerequisites a bisect needs to work
- Describe what a bisect result adds to a bug report
The Problem
A ticket arrives: "Refund totals are wrong for discounted items. This worked in the January release."
Between the January tag and today there are 380 commits. Somewhere in there, one of them broke it.
Reading 380 diffs is not a plan. Neither is asking around, though it is what usually happens — and it costs half a day and produces a guess.
The Insight
You do not need to read the commits. You only need to be able to test one.
Pick the commit in the middle of the range. Build it, run the reproduction, and answer one question: is the bug present?
- Yes — the bug was introduced somewhere in the first half. The second half is irrelevant.
- No — the bug was introduced somewhere in the second half. The first half is irrelevant.
Either answer eliminates 190 commits. Repeat on what is left: 190 → 95 → 48 → 24 → 12 → 6 → 3 → 2 → 1.
Nine tests to search 380 commits. That is binary search, and Git implements it as a built-in command.
The maths is generous. Doubling the range costs you exactly one more test:
| Commits | Tests needed |
|---|---|
| 100 | 7 |
| 1,000 | 10 |
| 10,000 | 14 |
| 100,000 | 17 |
A hundred thousand commits, seventeen tests. This is why bisect is worth learning properly.
The Three Prerequisites
Bisect needs three things, and being explicit about them saves a wasted afternoon.
1. A reliable reproduction. You must be able to look at a build and say bug or no bug, the same way every time. If the failure is intermittent, bisect will follow a flaky answer down the wrong half and confidently report an innocent commit. Nail the reproduction before you start.
2. A known-good commit. Somewhere the bug definitely was not. A release tag is ideal — v4.1 is a much better boundary than "some commit in January", because the tag is exact and everyone else can repeat your result.
3. A known-bad commit. Usually just HEAD, since that is where you found it.
Then verify both ends before starting. Ten minutes checking that the "good" commit is genuinely good is the highest-value ten minutes in the whole process — if it is not, every subsequent answer is built on a false premise.
Choosing the Good Boundary
There is a trade-off, and it goes the opposite way to most people's instinct.
A wider range is barely more expensive. Going back a year instead of a month adds one or two tests. A wrong boundary invalidates everything.
So: when in doubt, go further back. Pick a release you are confident about rather than the most recent one you vaguely remember working. The cost is one extra build; the alternative is a result you have to throw away.
What This Does For a Bug Report
Compare the two reports.
Without bisect:
Refund totals are wrong for discounted items. Worked in the January release, broken now. Steps to reproduce: …
With bisect:
Refund totals are wrong for discounted items. Introduced by commit
4c1f8ab, "refactor: extract discount calculation" (Maria, 12 March). The commit changes the rounding order inapplyDiscount(). Steps to reproduce: …
The second one skips the entire investigation. It names the commit, the author, the date and the likely line. It very often turns a two-day ticket into a twenty-minute fix.
And it costs you about half an hour, most of which is waiting for builds — which the next two lessons will show you how to automate away.
What Bisect Does Not Do
It finds where a behaviour changed, which is not always where the bug is.
A commit that merely exposed a latent defect will be the answer, and it will be a correct answer to the question you asked. The fix may well belong somewhere else entirely. Report the commit as "the change that introduced the symptom", not "the bug", and let the developer take it from there.
Pro Tip: Write the reproduction down as a numbered list before you start, and follow it identically at every step. Bisect is only as reliable as the consistency of your testing, and nine builds is more than enough for "I think I did it slightly differently that time" to creep in.
Key Takeaways
- Binary search halves the candidate range with every test
- 380 commits take nine tests; 100,000 take seventeen
- Bisect needs a reliable reproduction, a known-good commit and a known-bad commit
- Verify both boundaries before starting — a wrong "good" invalidates everything after it
- A wider range costs one or two extra tests; prefer it to an uncertain boundary
- A named commit turns a bug report into an almost-diagnosis
- Bisect finds where behaviour changed, which is not always where the defect lives