Git bisect: Find the bug-causing commit fast with binary search

Git tutorial - IT technology blog
Git tutorial - IT technology blog

When production breaks but you have no idea which commit did it

Sprint’s done, deployment goes out, and 30 minutes later Slack explodes: “There’s a bug — Feature A is broken!” You run git log… 47 commits in the sprint. Start reviewing them one by one? There goes your entire afternoon.

This happens more often than it should — on a 4-person microservices project, every sprint piles up dozens of new commits. The instinct is to git checkout each commit and test manually. Then I remembered git bisect — something I’d read about but never actually touched.

After that first time using bisect to track down a bug in 5 minutes instead of 3 hours, I never went back to the old way.

How git bisect works

The idea is simple: instead of checking commits one by one in order, git bisect splits the commit history in half and asks: “Does this commit have the bug?” You answer “yes” or “no”, and it keeps halving the remaining range. This is exactly binary search.

With 1000 commits, the worst case manually is 1000 checks. With bisect, the worst case is log₂(1000) ≈ 10 checks. With 47 commits? About 6 checks and you’re done.

The whole thing revolves around two simple labels:

  • bad: a commit that has the bug (usually the current HEAD)
  • good: a commit without the bug (an older commit you know was working fine)

Git bisect automatically checks out the middle commit, you test it and mark it bad or good — repeat until the culprit is isolated.

Step-by-step walkthrough with a real example

Starting a bisect session

Say you’re on the main branch, HEAD is the latest commit, and it has a bug:

# Start bisect
git bisect start

# Mark the current commit as bad (has the bug)
git bisect bad

# Mark an older commit as good — use a release tag or a specific hash
git bisect good v1.2.0
# or use a hash
git bisect good abc1234

Git immediately checks out the middle commit and tells you how many steps remain:

Bisecting: 23 revisions left to test after this (roughly 5 steps)
[f3d9a2b] Fix login validation

The test → mark loop

At each commit git brings you to, run your tests or manually reproduce the bug, then mark it:

# If this commit HAS the bug
git bisect bad

# If this commit does NOT have the bug
git bisect good

Keep going until git reports it found the culprit:

e4f5c6d is the first bad commit
commit e4f5c6d
Author: Nguyen Van A <[email protected]>
Date:   Mon Feb 10 14:32:11 2026 +0900

    Refactor user authentication module

:040000 040000 abc... def... M    src/auth

There it is. Commit e4f5c6d is the culprit.

Finishing and returning to HEAD

git bisect reset

This brings you back to your original HEAD. Always run this when you’re done — otherwise you’re sitting somewhere in the middle of history, which is easy to lose track of.

Automating with a script

This is my favorite part. Instead of manually testing each commit, you write a script and let git bisect run it automatically:

git bisect start
git bisect bad HEAD
git bisect good v1.2.0

# Run the script automatically — exit code 0 = good, non-zero = bad
git bisect run ./test_bug.sh

The test_bug.sh file is as simple as:

#!/bin/bash
# test_bug.sh — check whether the login feature works correctly
cd /path/to/project
python -m pytest tests/test_auth.py::test_login -x -q
# exit code comes automatically from pytest: 0 = pass, 1 = fail

Or with Node.js:

#!/bin/bash
npm run build 2>/dev/null && node -e "
const auth = require('./dist/auth');
const result = auth.validateUser('[email protected]', 'password');
process.exit(result ? 0 : 1);
"

Git bisect run will automatically execute this script on each commit and mark it bad or good based on the exit code. You just sit back and watch.

Viewing the bisect log

To review which commits were visited during the session:

git bisect log

If you need to include the investigation trail in a bug report, this is what to attach — your team lead won’t need to ask any follow-up questions.

Skipping commits you can’t test

Sometimes you hit a commit that won’t build — outdated dependencies, missing config, or unresolved conflicts. In that case:

git bisect skip

Git skips over it and tries a neighboring commit.

Practical notes from real experience

Pin down your “good commit” accurately: I use git tag to mark releases. No tags? Estimate: “This feature was working fine two weeks ago.” Then run git log --oneline --since="2 weeks ago" to find a suitable hash.

Unit tests are the force multiplier for bisect: On projects with solid test coverage, writing a bisect run script that targets the relevant test case is the fastest approach by far. I once tracked down a bug in just 8 minutes this way — fully automated, no babysitting required.

Watch out for merge commits: Bisect sometimes lands on a merge commit. You’ll need to look at both parent branches to understand the proper context instead of jumping to conclusions.

On the topic of caution — I once lost important code due to a force push on the wrong branch, and since then I’ve been much more deliberate with every git operation. Same goes for bisect: once you’ve identified the bad commit, don’t rush to git revert it. Read the diff carefully first. A lot of the time that commit touched multiple things at once, and reverting all of it introduces a completely different set of problems.

A practical workflow for handling bug reports

After debugging with bisect many times, I’ve settled on a consistent flow:

  1. Confirm the bug still exists at HEAD — run git status to make sure you’re on the right branch
  2. Find the “last known good” — usually the most recent release tag or the commit just before the current sprint
  3. Write a minimal test case that reproduces the bug if possible (to use with bisect run)
  4. Run git bisect start + git bisect run if you have a script, or mark manually if not
  5. Read the bad commit’s diff carefully with git show <hash> before writing a fix
  6. git bisect reset back to HEAD before you start writing the fix

Conclusion

Most developers know git bisect exists — but ask “when did you last use it?” and you get silence. That’s a waste.

After six months of regular use, bisect shines most in two situations. First, regression bugs after a long sprint with many commits. Second, large codebases where you don’t have enough context to know where to even start reading the code.

You don’t need to memorize complex syntax — just remember: bisect start → mark bad/good → test each commit → bisect reset. Next time you hit a bug and have no idea which commit caused it, try bisect instead of manually sifting through git log and see what happens.

Share: