Divide and Conquer Debugging
Halving the search space until the cause is isolated.
Also known as: bisecting, divide and conquer debugging, wolf fence algorithm
Divide-and-conquer debugging means narrowing a problem by repeatedly cutting the search space in half, until the cause is isolated. It’s the same idea as binary search: check the middle, decide which half contains the bug, and repeat.
In code
You know that the input is fine and the output is wrong, with a long pipeline between them. Check the value in the middle:
def process(order):
a = step1(order)
b = step2(a)
print("after step2:", b) # is this already wrong?
c = step3(b)
d = step4(c)
return d
- If
bis already wrong, the bug is in steps 1 or 2. Check between them next. - If
bis right, the bug is in steps 3 or 4.
Each check halves what’s left. A hundred lines becomes about seven checks.
In history
git bisect does it across commits: it checks out a commit halfway between “good” and “bad”, you test it, and repeat to find the commit that introduced the bug.
In other places
- Inputs: remove half of the data, or half the configuration, and see if it still fails (minimal reproducible example).
- Code: comment out half the feature, or disable half the plugins.
- Systems: is the request correct at the gateway, at the service, at the database?
- Time: it worked yesterday. What changed since?
Rules
- Reproduce the bug reliably first (reproducing a bug). You need a clear pass or fail check.
- Pick a good midpoint, a place where you can observe the state.
- Don’t change two things at once.
- Write down what you’ve ruled out.
When the bug is intermittent, or the check is unreliable, the halves mislead you, so improve the reproduction first. See debugging.