Git Bisect
Date: 2026-08-17
Binary search through commit history to find the one that introduced a bug. It turns “somewhere in the last 400 commits” into about nine checks, and it’s the most underused command in git.
git bisect performs a binary search across a range of commits, asking you to test each midpoint until it identifies the first commit where a known-good state became bad.
Why it’s worth knowing
commits to search checks needed
10 4
100 7
1,000 10
10,000 14
Doubling the history adds one check. Searching a thousand commits by hand is a day; bisect makes it ten builds — Time and Space Complexity.
The manual loop
git bisect start
git bisect bad # current commit is broken
git bisect good v2.4.0 # this tag worked
# git checks out a midpoint. Test it.
git bisect good # or: git bisect bad
# repeat until git reports the culprit
git bisect reset # back to where you weregit bisect reset is the step people forget, leaving the repository on a detached HEAD in the middle of history — Git Model.
Automating it
The version that makes bisect genuinely cheap:
git bisect start HEAD v2.4.0
git bisect run npm test -- checkout.test.jsGit runs the command at each step and reads the exit code — zero is good, non-zero is bad. Walk away and come back to the answer.
The script doesn’t have to be a test. Anything with an exit code works:
git bisect run ./scripts/check-bundle-size.sh
git bisect run sh -c 'npm run build && node check.js'Exit code 125 means “cannot test this commit” — use it when a revision doesn’t build for unrelated reasons, and bisect will skip it rather than treating it as bad.
What makes a repository bisectable
Bisect only works if arbitrary commits in history are testable, which is a property you build rather than get:
- Every commit on
mainshould build and pass. This is the practical argument for CI on every commit rather than only on pull requests — Continuous Integration - Atomic commits. A commit doing three things tells you the bug is in one of three places — Commit Hygiene
- A reliable test. A flaky test makes bisect produce a random commit with full confidence — Flaky Tests
- Small commits. Bisect narrows to a commit; if that commit changes 40 files you’ve saved less than it appears
Squash-merging every pull request reduces bisect resolution to one commit per feature — usually still a large win, and worth knowing as a cost — Merge vs Rebase.
Beyond bugs
The command searches for any transition, not only breakage:
- When did the bundle exceed the budget? — Performance Budgets
- When did this page get slow?
- When did this test start being flaky? — run it 20 times per step
- When was this behaviour introduced? —
git bisect old/newuses neutral terms for non-bug searches
git bisect start --term-old fast --term-new slowWhen it doesn’t work
- The bug is intermittent. You’ll mark a commit good that isn’t. Run the check several times per step, or accept the result is unreliable
- The bug depends on external state — a database, an API, a data migration
- History isn’t buildable — dependency changes that don’t restore cleanly at old commits
- The change was a configuration or data change, not a commit
The habit worth forming
Reach for bisect before reading code. The instinct is to reason about which change caused a regression; bisect answers it empirically in less time and without a hypothesis.
It’s also the strongest practical argument for the disciplines that make it possible — atomic commits, green main, reliable tests — which pay off here in a way that’s easy to demonstrate.