What Happens When You Type a URL and Press Enter
DNS lookups, TCP and TLS handshakes, HTTP requests, servers, and the browser's rendering pipeline. The whole journey of a web page, explained so it finally clicks.
Read →Debugging is not about being clever. It is a repeatable process: reproduce, observe, narrow down, fix the cause, and prove it. The method experienced developers use on any bug.
Watch a beginner and an experienced developer debug the same problem and you'll notice something. The experienced one isn't necessarily smarter. They're just less random. They follow a process, and that process works whether the bug is in a React component, a SQL query or a deployment script.
Here's that process.
You can't fix what you can't trigger. Before touching any code, find the exact steps that make the bug appear.
Write the steps down. If the bug only appears "sometimes," that's a clue in itself: timing, caching, race conditions, or data that differs between users.
The ideal result is a minimal reproduction: the smallest piece of code or the shortest set of steps that still shows the problem. Building one often reveals the bug before you even finish.
Error messages and stack traces are the most underused debugging tool there is. Read the whole message, find the first line in the stack trace that points to your code, and go there. If messages feel like noise, this guide helps: how to read error messages and stack traces.
Also check the places errors hide:
Don't change things at random. State a guess you can test:
"I think the user ID is undefined when the profile request is made."
A good hypothesis is specific and checkable. A bad one is "something's wrong with React."
Now gather evidence. Pick the cheapest tool that answers the question:
console.log, print, dd(). Log values and types, not just "got here." console.log({ userId, type: typeof userId }) beats console.log('here').If the evidence contradicts your hypothesis, good. You've learned something. Form the next one.
When you have no idea where the bug lives, cut the problem in half repeatedly.
For "it used to work," Git has this built in:
git bisect start
git bisect bad # current commit is broken
git bisect good v1.4.0 # this one worked
# Git checks out a commit in between; test it, then:
git bisect good # or: git bisect bad
Git keeps halving until it identifies the exact commit that introduced the bug. On a long history, that's a handful of steps instead of hours of reading diffs.
Say a value is sometimes null and crashes the page. The symptom fix is value?.name ?? ''. The real question is why is it null? Maybe a request races another. Maybe a database row is missing a foreign key. Maybe validation is absent.
Sometimes a defensive fix is the right call. But make it a decision, not a reflex. Symptom fixes tend to move the bug somewhere harder to find.
After you've reproduced it, read the errors, and tested a couple of hypotheses, ask. Include what you expected, what happened, the minimal reproduction, and what you've already ruled out. That's a question people want to answer, and half the time you'll solve it while writing it.
AI assistants are useful here too, if you give them the same context and verify what they suggest. More on that in using AI coding assistants without getting worse at coding.
Reproduce the bug reliably, read the full error message, then narrow the location by halving the problem space. Random code changes feel fast but almost always take longer.
Both. Logging is quick and works anywhere, including production. A debugger is better for complex state, because you can inspect everything at a paused moment and step through execution.
Add logging around the suspected area, capture context (user, input, timing, environment) in production with an error tracker, and look for patterns across occurrences until you can reproduce it.
DNS lookups, TCP and TLS handshakes, HTTP requests, servers, and the browser's rendering pipeline. The whole journey of a web page, explained so it finally clicks.
Read →There are dozens of HTTP status codes, but about fifteen cover nearly everything. What each one means, when to return it, and the mix-ups (401 vs 403, 301 vs 302) that confuse everyone.
Read →A, AAAA, CNAME, MX, TXT and NS records in plain English, why DNS changes seem slow, and the troubleshooting commands that tell you what is really going on.
Read →