debugging as a disciplined process
Imagine your kitchen tap is leaking. You could randomly tighten every visible screw and hope - or you could watch where the water actually comes from, turn things off one at a time to see which stops the drip, form a guess about the worn washer, replace it, and check that the drip is truly gone. The second way is debugging done as a discipline: not flailing, but a small loop of observe, narrow down, guess, fix, confirm. A bug is just a difference between what the program does and what you wanted; debugging is how you close that gap on purpose rather than by luck.
The loop has clear steps, and skipping them is what makes debugging feel like black magic. Reproduce: find a reliable, ideally small recipe that makes the bug happen every time, because a bug you cannot trigger on demand is one you cannot study. Isolate: shrink the situation - cut inputs, disable features, comment out code - until only the essential trigger remains, so you know roughly where to look. Hypothesize: state a specific, testable guess about the cause ('the pointer is NULL when the file is missing'), not a vague feeling. Test that one guess - with a debugger, a printout, an assertion - and let the result confirm or kill it. Fix the actual cause, then confirm by reproducing again and watching the bug fail to appear. Each step is cheap; doing them in order is what saves hours.
Why it matters: bugs in systems programming often corrupt memory silently and only crash much later, somewhere far from the real mistake, so guesswork is especially expensive here. A disciplined loop keeps you honest - it forces you to confirm the bug is really fixed, not merely hidden, and it stops you 'fixing' things that were never broken. The biggest trap is changing several things at once: change one variable per experiment, or you will never know which change mattered. Tools in this field - debuggers, sanitizers, tracers - are just ways to get sharper observations inside this same loop.
A program crashes 'sometimes'. Disciplined approach: first nail down reproduction - it crashes only when the input file is empty. Isolate to the read loop. Hypothesize the buffer is used uninitialized when zero bytes are read. Confirm with one print, fix the read check, then feed an empty file again and watch it run cleanly.
Reproduce -> isolate -> hypothesize -> fix -> confirm: one variable changed per step.
A fix that 'seems to work' after a change you do not understand has not been confirmed - the bug may have only moved or gone quiet. You have fixed it only when you can explain why the original reproduction now behaves correctly.