I think about it best by ruling out parts that couldn't be the problem. I start by trying to rule out as big of chunks as I can, which helps me narrow down to the subsystem, class, function, or even couple of lines where the problem lives. As soon as I can confirm that a piece works like I expect it should, I shrink my scope a little bit and look for the next piece to confirm works on its own. Once I find the spot that's working weird, it's super important that I understand why it's doing what it's actually doing so I can make sure the fix I use actually solves the problem.
Sometimes when I'm tired, I'll find myself randomly trying to change a piece of code to see if it works, but that's a sure sign that I'm not going to get anything else productive done and I need to take a walk, because it means I don't understand why my fixes should work.
It's nice because this thought process works really well for debugging mechanical assemblies that don't quite work too:
"OK, well we've verified that every dimension on this part is right, so take that out, set it aside, and look for the issue in the now-slightly-simpler assembly."
The most important part is that, the more confused and overwhelmed I get, the smaller, slower steps I take. :)
We're a place where coders share, stay up-to-date and grow their careers.
We strive for transparency and don't collect excess data.