There is an old joke that the two hard problems in computing are naming things, cache invalidation, and off-by-one errors. It endures because it is true: the off-by-one error — being one position too far or too short at a boundary — is among the most persistent bugs, hitting beginners and veterans alike. Boundaries are simply where our intuition is weakest.
You will never eliminate them entirely, but you can learn to expect and catch them.
Why boundaries confuse us
The trouble lives at edges: does a loop run to the last item or one past it, does a range include its endpoint, is a list counted from zero or one? Human intuition blurs these distinctions, and different languages and libraries make different choices, so a mental habit that works in one place quietly breaks in another. The classic symptoms are reading past the end of a collection, or missing the first or last element.
The bug is small precisely because it is a single step wrong — which also makes it easy to overlook.
How to catch them
The best defence is testing at the boundaries deliberately: the empty case, a single element, the first and last positions. Bugs that hide in the middle of a range almost always reveal themselves at the edges. When writing a loop or slice, pause specifically to ask what happens at the very start and the very end.
It also helps to prefer higher-level constructs — iterating over a collection directly rather than manually managing indices — because they remove many of the boundary decisions that invite the mistake.
Making peace with it
Off-by-one errors are not a sign you are a bad programmer; they are a permanent feature of working at the edges of ranges. The professionals do not stop making them so much as expect them, and build habits — boundary tests, careful reading of range semantics, index-free iteration — that catch them fast.
Treat every boundary as a place to slow down and check. That small ritual quietly prevents a large share of these ancient, stubborn bugs.