Read the error message
Seriously. Most developers skim error messages and jump to Stack Overflow. The error message is almost always telling you exactly what's wrong — it often names the file, the line, the violated constraint. I've caught myself skimming past a full stack trace to look for a blog post that paraphrases it.
Read it before you search for it.
Rubber duck debugging works
Explaining the problem out loud forces you to articulate your assumptions. Half the time, the act of explanation reveals the bug — because the assumption you've been making silently is the one that falls apart when you say it aloud. I've resolved incidents at 2am by explaining the problem to a text editor before writing a single diagnostic command.
This connects to My Development Philosophy — simplicity makes bugs easier to explain, which makes them easier to find.
Binary search your way to the bug
Comment out half the code. Does the bug still happen? If yes, it's in the remaining half. Repeat. This is O(log n) debugging and it's embarrassingly effective even on codebases you know well. The Mailcow priority-queue incident took an hour to diagnose. Most of that hour was systematically eliminating what it wasn't — Rspamd, TLS, rate limits — before the queue semantics became the obvious answer.
Check your assumptions, especially the certain ones
The bug is never where you think it is. It's in the code you're "sure" works. The most expensive debugging sessions I've had were ones where I spent two hours on the complex code before checking the obvious thing — a wrong environment variable, a stale cache, a port collision in the compose file. Question every assumption, especially around state, async timing, and anything involving network configuration.
Log at the boundaries
console.log debugging is fine. Strategic logging is better. Log at function entry and exit, at API call boundaries, at state transitions. A log line that says "entering payment flow with planId=undefined" is worth more than ten that say "here" and "there". See Tools I Can't Live Without for the observability stack I use in production — logs that are structured at write time are searchable at debug time.
The hard lesson
The most expensive debugging is the debugging you do on a system you don't understand well enough to rule things out quickly. Self-hosting mail infrastructure, operating 116 containers, building MCP tooling — all of it is debugging investment. The time spent understanding the system before something breaks is leverage when something does.