Chapter 7.2 — git bisect — Binary Search for Bugs☕ 18 min read

git bisect — Binary Search for Bugs

1000 commits ho gaye, aur achanak test fail ho gaya. Kaunsa commit ne toda? Bisect se 10 steps mein pakdo!

01The Bug Hunt Problem — Why Bisect Exists

A bug appeared in your code. You know it worked before, but now it is broken. Which commit introduced it?

The manual approach: check out each commit one by one, test, move to the next. For 100 commits, that is 100 tests. For 1000 commits, impossible.

git bisect uses BINARY SEARCH: cut the search space in half at each step.

1000 commits → 500 → 250 → 125 → 62 → 31 → 15 → 7 → 3 → 1. Found in approximately 10 steps!

The math: log2(1000) ≈ 10 steps. log2(1,000,000) ≈ 20 steps. Even a million commits → only 20 tests.

You only need to know TWO things: a commit where the bug EXISTS (bad), and a commit where the bug DID NOT exist (good).

Bisect is the most powerful debugging tool in Git that most developers have never used.

# The manual nightmare — checking every commit
git checkout HEAD~1    # test... still broken
git checkout HEAD~2    # test... still broken
git checkout HEAD~3    # test... still broken
# ... 47 more checkouts later ...
git checkout HEAD~50   # test... works! Bug is between 50 and 49
# Total time: hours. Energy: zero. Patience: gone.

# The git bisect way — binary search
git bisect start
git bisect bad                    # current commit is buggy
git bisect good v1.0.0           # this version worked
# Bisect checks out the middle commit
# You test it and say:
git bisect good                  # or git bisect bad
# Repeat ~10 times for 1000 commits
# Git finds the EXACT commit that introduced the bug
Binary search is exponentially faster than linear search. For N commits, linear search takes N steps. Binary search takes log2(N) steps. For 1000 commits: 1000 vs 10. For a million: 1,000,000 vs 20. The difference grows exponentially. This is the same algorithm that makes database indexes fast and dictionary lookups instant.
02How Binary Search Works in Git

Bisect maintains a range: [known-good ... known-bad]

At each step, Git checks out the MIDDLE commit of that range.

You test that commit and tell Git: good (bug not present) or bad (bug present).

  • If good: the bug was introduced AFTER this commit → search the upper half
  • If bad: the bug was introduced AT or BEFORE this commit → search the lower half

Git repeats: middle → test → narrow range → middle → test → narrow range → found!

When the range narrows to one commit, Git announces: "X is the first bad commit"

Git shows you the diff of that commit — this is the change that introduced the bug.

You can then git show <hash> to see the full commit details.

# Example: 1000 commits, bug introduced at commit #623
git bisect start
git bisect bad HEAD          # commit 1000 is bad
git bisect good HEAD~999     # commit 1 is good

# Step 1: Git checks out commit 500 (middle)
# You test: it works
git bisect good
# Range is now: commit 501 to 1000

# Step 2: Git checks out commit 750 (new middle)
# You test: it is broken
git bisect bad
# Range is now: commit 501 to 750

# Step 3: Git checks out commit 625 (new middle)
# You test: it is broken
git bisect bad
# Range is now: commit 501 to 625

# Step 4: Git checks out commit 563 (new middle)
# You test: it works
git bisect good
# Range is now: commit 564 to 625

# ... continues narrowing ...

# Step 10: Git finds it!
# abc1234 is the first bad commit
# abc1234 is commit #623 — the exact one that introduced the bug!
03The Bisect Workflow — Step by Step

Step 1: git bisect start — begin the bisect session

Step 2: git bisect bad — mark current HEAD as bad (bug exists here)

Step 3: git bisect good <hash-or-tag> — mark a known-working commit as good

Step 4: Git automatically checks out the middle commit

Step 5: You TEST the checked-out code (run the app, run tests, reproduce the bug)

Step 6: Report to Git: git bisect good (no bug) or git bisect bad (bug exists)

Step 7: Repeat steps 5-6 until Git says "X is the first bad commit"

Step 8: git bisect reset — EXIT bisect and return to your original branch

CRITICAL: Do not forget git bisect reset! Without it, you are stuck in a detached HEAD state.

💡 Pro Tip: During bisect, Git puts you in a detached HEAD state at each middle commit. This is normal — you are time-traveling through history. Just test the code and report good/bad. DO NOT make commits during bisect (unless you are fixing the bug to test). Always run git bisect reset when done to return to your branch.
04Practical Bisect — Real-World Example

Scenario: Your React app crashes on page load. It worked fine 2 weeks ago.

Step 1: Find a known-good reference. A tag (v2.1.0) or a date-based ref (git log --before="2024-01-15" -1).

Step 2: Start bisect with the known bounds.

Step 3: At each step, run your app or test suite to check if the bug exists.

Step 4: Once bisect finds the culprit, examine that commit carefully.

Pro tip: if you can write a test that fails for the bug, use git bisect run (next chapter) to fully automate this.

After finding the commit: git show <hash> → read the message → understand the change → fix the bug.

# Real-world scenario: App crashes on login page
# Last known working version: v2.1.0 (2 weeks ago)

# Start bisect
git bisect start

# Current code is broken
git bisect bad HEAD

# v2.1.0 was working
git bisect good v2.1.0
# Bisecting: 87 revisions left to test after this
# [checkout middle commit]

# Test the app
npm start
# App crashes! Bug exists here
git bisect bad
# Bisecting: 43 revisions left to test after this

# Test again
npm start
# App works fine! No bug here
git bisect good
# Bisecting: 21 revisions left to test after this

# Continue testing...
# After several more iterations:

# a1b2c3d4 is the first bad commit
# commit a1b2c3d4
# Author: Ravi Kumar
# Date:   Mon Jan 22 10:30:00 2024
#
#     feat: add new auth middleware

# Found it! Ravi's auth middleware change introduced the bug
git show a1b2c3d4  # see the full diff

# Return to normal
git bisect reset
05Bisect Tips and Edge Cases

Use tags or branch names instead of hashes for good/bad markers — easier to remember.

git bisect skip — if a commit cannot be tested (build broken, unrelated error), skip it. Git tries a nearby commit.

git bisect log — see the history of your bisect session (which commits you marked good/bad).

git bisect replay <logfile> — redo a bisect session from a log file (useful if you made a mistake).

git bisect visualize — see which commits are left to test in gitk or git log.

If you accidentally mark a commit wrong, you cannot undo it directly — use git bisect log > logfile, edit the file to remove the wrong entry, then git bisect reset and git bisect replay logfile.

Non-deterministic bugs (intermittent) are hard to bisect — the test must be reliable.

Bisect is incredibly powerful but underused. In a survey, less than 20% of developers use git bisect regularly. Those who do find bugs 10x faster. The key insight: you do not need to understand the code to find when a bug was introduced. You just need a reliable way to TEST if the bug exists. Let the computer do the searching — that is what computers are for.

Lo kar liya — Key Points:

  • ✅ git bisect uses binary search to find which commit introduced a bug — log2(N) steps instead of N
  • ✅ You need a known-bad commit (bug exists) and a known-good commit (bug does not exist)
  • ✅ At each step, Git checks out the middle commit — you test and report good or bad
  • ✅ 1000 commits = ~10 steps, 1 million commits = ~20 steps — binary search is exponentially faster
  • ✅ Always run git bisect reset when done to return to your original branch
  • ✅ Use git bisect skip when a commit cannot be tested (broken build, unrelated error)
  • ✅ Bisect works with any testable condition — crashes, failing tests, visual bugs, performance regressions
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase