Debugging the Web the Right Way: A Method, Not a Guess

July 12, 2026

5 min read

Localize the failure, then walk up and down the dependency tree

A checkout bug landed on my desk that came down to exactly one cent.

Customers could pay with loyalty points — drag a slider to decide how many points to put toward an order, watch the total come down. Across most of the slider's range it was fine. At one specific position it wasn't: the points said the order was fully covered, and checkout still charged a cent.

The wrong number was right there on the screen, on the slider, so every instinct said go fix the slider. That instinct is the trap. The slider was rendering exactly what it was handed. The bug was a rounding step further down, where points got converted to a dollar amount and rounded in one place and not the other. At that one increment, the discount the UI showed and the amount the server charged disagreed by a cent. I could have spent a day inside that component and never once touched the cause.

Finding it wasn't about being clever. It was walking back from the number on the screen to the place the number was actually made.

Most "debugging" I watch is the opposite of that: change something near where the problem appears, refresh, and hope. It works often enough to keep you doing it, which is the worst thing about it. Here's the method I use instead. It's just refusing to guess.

Reproduce It First, or You're Not Debugging Yet

The first rule is the one people most want to skip: make it happen on demand before you change anything.

If you can't reproduce it, you can't tell whether your fix worked or whether the bug just went quiet for a minute. You'll "fix" it, ship it, and it comes back on Thursday.

So I get a reliable repro before I touch a line: the exact steps, the exact account, the exact payload. If I can't trigger it myself, I get a screen recording from whoever can. A recording of the failure happening is worth more than a paragraph describing it, because it shows you the things the reporter didn't think to mention.

No repro, no debugging. You're just editing.

Read the Evidence Before You Touch Anything

Once it reproduces, I resist the urge to open the code. First I look at what the running app is already telling me, in order:

  1. Network. What actually went over the wire? Status codes, the real request payload, the real response body. Half of "frontend bugs" are the backend returning something the frontend never expected.
  2. Console. Errors, warnings, the stack trace nobody read. The stack is a map to the scene. Start at the top.
  3. The DOM. Inspect the element that's wrong and read the tree around it for location cues — which component owns it, what state it's bound to, what's conditionally rendering.

When that isn't enough, I add logs at the boundaries or set a breakpoint in Chrome and step through. But logs and breakpoints come after the free evidence, not instead of it. The evidence is already on screen. Read it before you add your own.

Find Where It Breaks, Then Walk Both Ways

Here's the move that separates debugging from guessing.

You know where the failure is observed: the wrong number on the screen. That is almost never where the failure is. So I mark the observation point and walk the dependency tree in both directions:

  • Down: into what feeds this. Where did this value come from? The component got it from a hook, the hook from a request, the request hit an endpoint, the endpoint read a cache, the cache read the database. I follow the value backward until it stops being wrong. The step where it becomes wrong is the bug.
  • Up: into what depends on this. If this value is off, what else consumes it? Sometimes the fastest way to locate a cause is to notice everything else that's subtly broken and find the one thing they share.

A typical request chain: the symptom shows at the component, but the value only goes wrong upstream

In the redemption bug, the wrong number was the total under the slider. Walking down from it, into the step that converted points to dollars, is what surfaced the mismatched rounding. Staring at the slider never would have.

This is also exactly the trap I keep watching AI agents fall into: they fixate on the layer where the symptom shows up and refuse to walk upstream. I've had an agent spend its whole turn "fixing" an inference step in a pipeline when the real problem was the extraction step feeding it garbage. Same failure as reaching for that slider: staring at the symptom, never traversing to the source.

Let the Tests Do Some of the Work

If there are tests around the suspect area, I use them as a probe.

Feed the failing input — and inputs near it — straight into the unit under suspicion. A test that passes on the value you expected and fails on the value from production tells you two things at once: the bug is real, and it's here, at this boundary. It turns "I think it's around here" into "it's this function, on this input."

This is where clean layer boundaries pay off. If each layer has a defined shape going in and out, you can pin the failure to one layer by feeding it the bad input in isolation, no full app required.

Have a Hypothesis — Then Try to Kill It

Once the evidence points somewhere, I write down what I think is happening. One sentence: "The UI rounds the points-to-dollars conversion one way and the server rounds it another, so at that one slider step they split by a cent."

Then I try to prove myself wrong.

Guessing looks for confirmation — you suspect a thing, you find one detail that fits, you declare victory, you're often wrong. The discipline is to actively attack your own hypothesis. If I'm right, then running that one value through each rounding path should produce two different cents, and every other value should produce the same. So I run the conversion both ways across the whole slider range. If the two numbers agree at the failing step, the rounding theory is dead and I've saved myself an afternoon of "fixing" the wrong thing.

A hypothesis you only tried to confirm isn't a diagnosis. It's just a guess that happened to agree with you.

Draw the Thing

When a bug spans more than two or three moving pieces, I stop trying to hold it in my head and I draw it. A quick map, sometimes an actual sketch on paper — the components, the requests, the services, the data, and the arrows between them.

The picture isn't the point. Drawing forces you to admit what you actually know versus what you're assuming. The gap in the diagram (the arrow you can't confidently draw) is usually where the bug lives. Every time I've been truly stuck, it's been because I was carrying a wrong mental model, and I only caught it once I had to put the model on paper and it didn't hold together.

Why This Is the Part AI Can't Do for You

I lean on AI agents every day, and I've written before about building a whole tool just to debug the LLM kind. But watch an agent debug and you'll see it do every single thing on the wrong-way list: it changes code it never reproduced the bug against, fixates on the layer where the symptom appears, confirms its first guess instead of attacking it, and announces the fix with total confidence whether or not anything got better.

The method above is the exact inverse of that. Reproduce before you touch. Read the evidence. Walk away from the symptom to find the cause. Try to prove yourself wrong. Draw the model so you can see where it's thin.

None of it is about being smarter than the bug. It's about refusing to guess. Still a human's job. That's the thing I most want a junior to take from me: the goal isn't to fix it fast, it's to understand it before you change it. Do that, and the fix is usually the easy part.