Job support7 min read

    Stuck for three hours? Debug in this order instead

    A repeatable debugging method: read the whole error, find what changed, prove where it breaks, and shrink the reproduction until it is obvious.

    Most developers do not have a debugging problem. They have an ordering problem.

    Faced with something broken, the instinct is to change what looks suspicious and see if it helps. Sometimes it does, and you learn nothing. Usually it does not, and you have added a variable to a situation you already did not understand. Three hours later there are six modified files and the bug has moved.

    The alternative is not cleverness. It is a fixed order, applied even when — especially when — you are under time pressure. This is that order.

    First, read the whole error

    Not the last line. The whole thing.

    Under pressure people read the final sentence, decide it is meaningless, and start guessing. But a stack trace is a narrative told backwards, and the useful part is rarely at the end.

    Read it looking for four things:

    The type of failure. A null reference, a type mismatch, a timeout and a permission denial suggest completely different causes. The category alone rules out most of the search space.

    The first line that is your code. Frameworks fill traces with their own frames. Scan down to the first file you recognise. That is nearly always where to start, even when the exception was raised deeper.

    The "caused by" chain. Many languages wrap exceptions. The outermost is often generic; the root cause is several layers down and much more specific.

    The actual values. Errors frequently contain the offending input. "Cannot parse date '2026-13-45'" hands you the answer. People skim past this constantly.

    If the message is genuinely unhelpful, search the exact string in quotes. Someone has had it before. Skip the content-farm results and look for the issue tracker or the library's source.

    Second, ask what changed

    Working software that stops working means something changed. Your job is to find out what, and this is the highest-yield question in debugging.

    Four candidates, in rough order of likelihood:

    Your code. Check your diff, actually read it, and do not trust your memory of what you changed. If you have a commit where it worked, git bisect will find the breaking commit mechanically — it is underused and it is much faster than reasoning.

    Your dependencies. Did a lockfile update? Did a colleague add a package? Did a deployment pull a newer minor version because a range allowed it? A broken build with no code change is very often this.

    Your environment. Environment variables, config files, a service that is not running locally, a database that has migrated but your local copy has not, a certificate that expired.

    Somebody else's system. A third-party API changed a response, started rate limiting, or is simply down. Check their status page before you spend an hour debugging your own client.

    If the honest answer is "nothing changed", it usually means the trigger is in the data rather than the code. The code has always had this bug; today it received the input that reaches it.

    Third, prove where it breaks

    This is the step that separates narrowing from guessing.

    You have a suspicion. Do not act on it — test it. Put a log line before the suspicious section and one after, and print the values you care about:

    console.log("before charge:", { orderId, amount, currency });
    // ...the code you suspect...
    console.log("after charge:", { result });
    

    Now you know something. Either the code reached the second line or it did not, and either the values were what you expected or they were not. Both outcomes eliminate half the search.

    Then repeat. Each round should halve the remaining space. Five or six rounds will take you from "somewhere in this service" to "this line, with this value" — which is usually fewer rounds than one hour of guessing.

    Three habits that make this work:

    Log values, not milestones. "here 1", "here 2" tells you the path. The values tell you the cause.

    Log the type as well when things are odd. typeof, repr(), or the equivalent. A surprising number of bugs are a string where a number was expected, and "5" and 5 print identically.

    Check your assumptions first, not last. "Obviously the user id is set by now" is the kind of thing that is wrong for forty minutes before anyone checks. Cheap to verify, expensive to assume.

    Use a debugger if you have one attached — stepping through beats log lines for inspecting complex state. But logs are better for sequences, timing, and environments you cannot attach to, so do not treat the debugger as always superior.

    Fourth, shrink the reproduction

    Once you can point at a line, make the smallest thing that still fails.

    Strip away everything not required: other fields, other records, other services, the UI. Keep removing until removing anything makes it stop failing.

    This is worth the effort for three reasons. A small reproduction usually makes the cause obvious — the act of shrinking is often the act of finding it. It proves your fix, because you can run it before and after. And if you need to ask for help, or file a bug against a library, it is the difference between an answer in minutes and being ignored.

    If you cannot reproduce it at all, that is now the problem you are working on. An unverifiable fix is a hope. Find what differs between the environment where it fails and yours — data shape, configuration, versions, permissions, concurrency — and close that gap until it breaks locally too.

    Fifth, fix the cause, not the symptom

    You have found it. Before changing anything, ask: why did this happen?

    If a null reference blew up, the shallow fix is a null check. But why was it null? If a record should always exist and did not, adding a check hides a data problem that will resurface somewhere less convenient.

    Three useful questions:

    • Why was the value wrong? Follow it back to where it came from.
    • Where else could this happen? If one call site forgot to handle this case, others probably did too.
    • What would have caught this earlier? A type, a validation at the boundary, a test. If the answer is obvious and cheap, do it while you are here.

    Then write a test that fails before your fix and passes after. Even one. It is the only way to be sure you fixed the thing you thought you fixed, and it is the only thing that stops it coming back.

    A note on the pressure

    Everything above assumes clear thinking, and clear thinking is exactly what disappears when a deploy is blocked and someone is asking for an update.

    Two things genuinely help.

    Write down what you know. Physically, in a scratch file: the symptom, what you have ruled out, what you tried, what happened. Under stress you will otherwise retry the same thing twice and forget a result you had ten minutes ago. It also makes you honest about whether you are still learning anything.

    Take the break. The twenty-minute walk that solves the bug is a cliché because it is true. Fixation is a real failure mode; you keep re-reading the same function because you have decided the answer is there. Stepping away breaks the loop. If it is 2am and you are changing things at random, you stopped debugging a while ago.

    Asking for help well

    There is a point where continuing alone is the expensive option. The signal is not the clock — it is whether your last few attempts produced any new information. If they did not, you are guessing.

    When you ask, bring:

    1. What you expected, and what happened, specifically.
    2. The actual error, complete, in text — not a phone photo of a screen.
    3. What you have ruled out, which is the part that saves the most time.
    4. The smallest reproduction you have.
    5. What changed, if you know.

    That takes five minutes to assemble and it routinely turns an hour of back-and-forth into a two-minute answer. It also does something useful for you: about a third of the time, writing it down surfaces the answer before you send it.

    And if you genuinely have nobody to ask — you are the only developer, or the team expects you to already know this — that is precisely the gap technical and job support exists to fill. Not a course, and not somebody writing it for you: working through the actual blocker on the actual project, so you understand the fix rather than pasting it.

    The short version

    1. Read the whole error, including the values.
    2. Find what changed: your code, dependencies, environment, or theirs.
    3. Prove where it breaks. One log before, one after. Halve the space each time.
    4. Shrink the reproduction until nothing can be removed.
    5. Fix the cause, then write the test.

    The order is the method. Applied consistently it makes hard bugs tractable, and it makes you faster at the easy ones too — which, on most days, is where the time actually goes.

    Common questions

    Time-box it, and make the box about progress rather than the clock. A useful rule is thirty to sixty minutes of genuinely new information. If your last three attempts taught you nothing you did not already know, you are guessing, and more time will not help. Ask then — but ask with the narrowing you have already done, which is what makes the answer fast.

    Then that is the bug you are working on first. An unreproducible bug cannot be verified as fixed, so any change you make is a hope rather than a fix. Work out what differs between the environments — data, configuration, versions, timing, permissions — and make your local environment resemble the broken one until it breaks too.

    They solve different problems. A debugger is better for inspecting complex state at one moment. Logging is better for understanding a sequence, for anything timing-dependent, and for problems that only occur in an environment you cannot attach to. Being fluent in both is worth the investment; most developers under-use the debugger.

    Want this looked at properly?

    Bring the actual blocker from the actual project. We will work through it with you so you understand the fix, not just the patch.

    Ask on WhatsApp

    Related services

    Keep reading

    All articles
    Job support8 min read

    Your first week in an unfamiliar codebase

    The instinct is to start reading files. On any real codebase you will read for three days and retain nothing. Do this instead.

    Read