Every developer eventually runs into the same unpleasant scenario.
The application worked perfectly a week ago.
Now something is broken.
Tests fail.
The UI behaves strangely.
An API endpoint returns nonsense.
And during that week?
Forty commits landed from multiple developers.
Maybe eighty.
Maybe three hundred.
You could inspect commits one by one.
Open diffs.
Run builds.
Test manually.
Slowly lose your patience.
Or you could let Git do the hard work.
That’s exactly what git bisect exists for.
Instead of checking commits sequentially, Git performs a binary search through commit history.
You tell Git:
this version works
and
this version is broken
Git then jumps directly to the middle of the range and asks:
Does it work here?
After several answers, the offending commit appears.
Forty commits?
Roughly six checks.
Eight hundred commits?
Around ten.
That’s the power of binary search applied to version control.
The Core Idea: Binary Search for Broken Code
If you’ve ever searched through a sorted array using binary search, the logic will feel familiar.
Suppose you have twelve commits:
c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11You know:
-
c0worked -
c11is broken
Instead of checking:
c1 → c2 → c3 → c4 → c5 ...Git immediately chooses the midpoint.
Something like:
c5You test it.
If c5 works:
the bad commit must be later.
If c5 fails:
the problem must be earlier.
Each answer cuts the search space in half.
That’s why git bisect becomes incredibly efficient on large histories.
Starting a Git Bisect Session
Begin by starting a bisect session.
git bisect startNow tell Git:
the current state is broken.
Usually this means your current HEAD.
git bisect badNext, mark a known working commit.
You can use:
- a commit hash
- a branch reference
- a tag
- an older release version
Example:
git bisect good v1.2.0At this point, Git calculates the midpoint automatically and checks out a commit somewhere between those two states.
You’ll see something like:
Bisecting: 4 revisions left to test
Checking out commit abc1234Now your job is simple:
test the application.
Testing Each Candidate Commit
Git has moved you to a candidate commit.
Now answer one question:
does the bug exist here?
If everything works:
git bisect goodIf the bug is present:
git bisect badGit immediately narrows the search range and jumps again.
You repeat the cycle:
- test
- mark good or bad
- test again
- mark again
Eventually Git reaches the answer.
When Git Finds the Guilty Commit
After several rounds, Git stops and reports the result.
Example:
a3f8c12 is the first bad commitThat commit introduced the problem.
You now know exactly where to investigate.
Instead of debugging forty random changes, you’re examining one specific diff.
That alone can save hours.
A Practical Example
Imagine this timeline:
c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11A week ago:
c0Everything worked.
Today:
c11Production is broken.
Start the session:
git bisect start
git bisect bad
git bisect good c0Git chooses the midpoint.
Example:
Checking out c5You run the app.
Suppose:
c5 still works.
Mark it:
git bisect goodGit shrinks the search window.
Now it may jump to:
c8You test again.
Suppose:
c8 fails.
Mark it:
git bisect badSearch space shrinks again.
After a few iterations:
Git identifies the exact commit responsible.
You never manually walked through the entire history.
Don’t Forget to Reset Afterwards
This is easy to forget.
During a bisect session, Git repeatedly checks out temporary commits.
Once you’re finished, return to your original branch state:
git bisect resetWithout reset, you may wonder why your repository suddenly sits on some detached historical commit.
Always reset when you’re done.
Automating Git Bisect with Tests
Manual testing works.
Automated testing works even better.
If you already have a command that verifies correctness and returns:
0 → success
non-zero → failureyou can automate the entire search.
Example:
git bisect start
git bisect bad HEAD
git bisect good v1.2.0
git bisect run npm testNow Git handles everything.
For each candidate commit it will:
- checkout commit
- run tests
- interpret result
- continue searching
No manual interaction required.
This becomes incredibly powerful for:
- failing unit tests
- broken builds
- regression bugs
- CI debugging
- flaky integrations
Using Custom Validation Scripts
You aren’t limited to npm test.
Any script can drive bisect.
For example:
git bisect run ./check-build.shor
git bisect run pnpm lintOr a custom Node validation script:
git bisect run node verify-api.jsAs long as the command returns a proper exit code, Git can automate the search.
That flexibility makes git bisect useful far beyond traditional testing.
Why Developers Forget This Tool Exists
git bisect is oddly underused.
Many developers know it exists but rarely reach for it.
Instead they default to:
- reading commit history manually
- guessing likely changes
- debugging recent merges
- blaming dependencies
- reopening old PRs
Sometimes that works.
Often it wastes time.
git bisect gives you something much stronger:
a systematic search strategy.
No guessing.
No intuition games.
Just elimination.
When Git Bisect Becomes Especially Valuable
This tool shines in situations like:
Regression Bugs
Something used to work.
Now it doesn’t.
Classic bisect territory.
Large Teams
Many developers.
Many commits.
Unclear ownership.
Bisect dramatically reduces noise.
Old Codebases
You don’t remember when the behavior changed.
The repository history does.
CI Failures
A build started failing somewhere in the last two weeks.
Nobody knows where.
Bisect knows.
Final Thoughts
git bisect is essentially binary search applied to commit history.
That simple idea makes it incredibly effective.
Instead of checking commits one after another, Git repeatedly cuts the search range in half.
Forty commits?
A handful of checks.
Hundreds of commits?
Still surprisingly manageable.
The workflow is simple:
Start:
git bisect startMark broken state:
git bisect badMark known working state:
git bisect good <commit>Test.
Answer.
Repeat.
Then finish:
git bisect resetThe next time somebody says:
“It worked last week, but now everything is broken…”
you won’t need to manually inspect fifty commits.
Let Git hunt down the culprit for you.