When a debugger is unavailable, it is tempting to scatter print statements through every suspicious function. The output grows quickly, but the question often stays vague: why is this broken?
A better first move is to write down the smallest observable mismatch. “The API is wrong” is broad. “This request returns an empty list when the database contains one matching row” gives the investigation a boundary.
Keep one reproduction still
Save the request, relevant configuration and expected result. Remove unrelated inputs until the failure stops shrinking. If the problem is intermittent, record what changes between runs instead of silently treating one successful run as a fix.
Then choose one hypothesis. Perhaps the identifier changes type between request parsing and the database query. Check the value and type at those two boundaries. If both are correct, that hypothesis has earned its retirement.
| |
This example deliberately records the type without dumping the entire request. Inspect only the fields needed to answer the question; a diagnostic log should not become a copy of every user’s data.
Find the first wrong state
Trace a short chain: input, parsed value, query parameters, query result, response. If the query result is already empty, changing response serialization is unlikely to help.
For a longer pipeline, inspect the middle first. A correct midpoint moves the search downstream; an incorrect midpoint moves it upstream. You do not need a trace of every line to eliminate half the possibilities.
Once the cause is understood, turn the reproduction into a regression check when the failure is worth protecting against. Remove temporary noise and retain the diagnostic context that would make the same incident understandable next time.
The useful output of debugging is an explanation: this input met this condition, which caused this wrong result. A code change should follow from that explanation.