This is the first post on my dev notes blog. I’m starting it to capture the things I learn day to day — the debugging sessions, the small realizations, the fixes that finally worked. Mostly for my future self, but you’re welcome to read along.
Let me kick it off with the single habit that has saved me the most time.
Table of contents
Open Table of contents
Treat every bug like a crime scene
When something breaks, the temptation is to start changing code immediately. You have a theory — “it’s probably the cache” — and a beautiful theory is seductive. But a theory that isn’t backed by evidence leads to random fixes that happen to work once and break again next week.
So I follow a simple loop, borrowed from detective work:

- State the crime. What exactly is wrong? Be precise. “It’s slow” is not a crime; “the
/ordersendpoint takes 4s when it used to take 200ms” is. - Form a theory. What could cause this? List a few suspects.
- Collect evidence. Add minimal, targeted logging or measurement. Do not fix anything yet.
- Let the evidence convict. Only when the data proves the theory do you make the change.
- Fix the root cause, not the symptom.
A worked example
Say an API call intermittently returns stale data. The seductive theory is “the cache TTL is too long.” Tempting to just lower it and move on. Instead, collect evidence first:
// Evidence-gathering, not a fix.
// Log the cache key, whether it was a hit, and how old the entry is.
function getOrder(id: string): Order {
const key = `order:${id}`;
const entry = cache.get(key);
console.debug("[cache]", {
key,
hit: Boolean(entry),
ageMs: entry ? Date.now() - entry.storedAt : null,
});
if (entry) return entry.value;
const fresh = db.fetchOrder(id);
cache.set(key, { value: fresh, storedAt: Date.now() });
return fresh;
}
Run it, reproduce the bug, read the logs. Maybe you discover the cache key collides across users — a completely different crime than “TTL too long.” Now the fix is obvious and correct, and you didn’t waste an afternoon tuning a TTL that was never the problem.
The fix changes because the theory was beautiful but unproven. Evidence first.
Why fail fast helps here
A related habit: throw errors at the boundary instead of silently papering over bad state.
function assertOrder(value: unknown): asserts value is Order {
if (!value || typeof value !== "object" || !("id" in value)) {
throw new Error(`Expected an Order, got: ${JSON.stringify(value)}`);
}
}
When preconditions aren’t met, you want to know immediately, at the source — not three layers downstream where the stack trace is useless. Fast failure turns a mystery into a labeled clue.
What’s next
That’s the method. Future posts will be the actual cases — the bug, the theory, the evidence, the fix. If a post helps you avoid one wrong fix, it earned its keep.
Thanks for reading.