How to Fix Any Bug: A Systematic Framework

Learn the step-by-step reduction process Dan Abramov uses to solve frustrating software bugs when AI and guessing fail.

18 cards · 3 min · tap to begin

From · · 3 min

How to Fix Any Bug: A Systematic Framework

Learn the step-by-step reduction process Dan Abramov uses to solve frustrating software bugs when AI and guessing fail.

In brief

Learn the step-by-step reduction process Dan Abramov uses to solve frustrating software bugs when AI and guessing fail. When theories fail, systematically delete code piece by piece while maintaining a broken repro until the root cause is isolated. Originally reported by Overreacted.

A mysterious scroll bug

While building an app, adding a server call to a button click suddenly broke page scrolling, turning smooth movement into erratic jitter.

So, adding a remote call somehow broke scrolling.

Challenging safe assumptions

Re-rendering in React should always be safe, and refetching data shouldn't disrupt an ongoing scroll animation, pointing to a hidden flaw.

In React, a re-render should always be safe to do.

Delegating blindly to AI

Asking an AI assistant to fix the bug directly led to a series of rewrites, each accompanied by false declarations that the issue was solved.

Each time, Claude would proudly declare that the problem was solved.

A common engineering mistake

AI models repeatedly fail in the exact same way human engineers do when they attempt fixes without a clear, repeatable verification path.

Claude was repeatedly wrong because it didn’t have a repro.

Defining a true repro

A reproduction case is a reliable set of instructions stating what to do, what to expect, and what actually happens during execution.

A repro... is a sequence of instructions then, when followed, gives you a reliable way to tell whether the bug is still happening.

The cost of flaky repros

Unreliable bugs force endless re-testing and guesswork, making high-fidelity, reproducible test conditions essential for rapid progress.

If my repro was unreliable... I’d either have to gradually remove different sources of uncertainty... or live with the producitivity hit.

Sensory limits in debugging

The initial repro relied on visual observation of scrolling jitter, something an AI without eyes or direct perception could not evaluate.

The problem is that 'scrolling jitters' from my repro didn’t mean anything to Claude.

Trading repros for progress

When a bug cannot be observed directly, you can swap your original reproduction case for a simpler proxy test that moves you forward.

You can trade a repro for another repro as long as you’re able to convince yourself that it’ll help you make progress on the original problem.

Narrowing to a proxy metric

A new automated test measured the exact scroll coordinates before and after clicking the button using Playwright instead of relying on sight.

Measure the document scroll position. Click the button. Measure the document scroll position again.

The trap of false proxies

Proxy repros carry the risk of testing an unrelated timing artifact or reading state too early, leading developers to reject working solutions.

One common pitfall with narrowing a repro is that you think you found a good one, but actually your new repro captures some unrelated problem.

Validating with positive controls

To confirm a proxy repro is accurate, verify that applying a known workaround, like commenting out the server call, produces a passing result.

This is why, whenever you narrow a repro, you should also confirm that a positive result... is still possible to obtain with the new repro.

Systematic reduction protocol

Systematically pare down code step-by-step, committing only when the bug persists, and reverting immediately whenever a change hides it.

Remove something from the relevant code... If the bug is still there, commit the changes.

The trap of early theories

AI and humans frequently abandon the broken codebase to build side repros based on premature hypotheses, losing sight of the active bug.

Claude got too carried away testing its own theories and ended up with a bunch of test cases that don’t actually exhibit the bug.

Applying well-founded recursion

Guaranteed progress requires monotonic reduction, ensuring every commit shrinks the code surface while continuously preserving the reproducible bug.

You want to know that you’re always, always making incremental progress and the repro keeps getting smaller.

Isolating environmental scope

Stripping down components until the code was placed in a single file revealed that the bug only occurred when nested inside a root layout.

Something was breaking when it was nested inside the root layout.

Spotting the unexpected culprit

The root cause was React Router's ScrollRestoration component, which buggily triggered on data revalidations instead of only full route changes.

Since my network call (via an action) revalidated the route, it triggered ScrollRestoration during scrollIntoView, causing the jitter.

Hidden dependency traps

AI scaffolding tools can quietly pin outdated versions of core libraries, introducing fixed upstream bugs into fresh projects.

The project was set up by Claude which for some inexplicable reason decided I should use an old version of a core dependency.

The power of elimination

When theories run dry, systematically deleting code while preserving a broken repro will always isolate the true cause of any bug.

This exact workflow—removing things one by one while ensuring the bug is still present—saved my ass many times.

The one thing

When theories fail, systematically delete code piece by piece while maintaining a broken repro until the root cause is isolated.

Read the original on Overreacted

React

Sign in to react and comment.

Comments (0)

Life is short. Keep it sweet. Respect others' opinions and be kind!

    Recommended next

    More decks on debugging and related topics.