Off-by-one
The bound is one out, and the code still runs.
What it looks like
- A loop condition using
<=where the collection is zero-indexed, or<where the bound is inclusive. lengthused as an index, orlength - 1used as a count.- A slice or range whose end is exclusive in one place and inclusive in another.
Why it survives review
Reading one position past the end of an array gives undefined rather than throwing, so nothing crashes at the site of the mistake. The failure surfaces later, somewhere that had nothing to do with it, and by then the loop looks innocent. A test with three elements that checks the first and the total will pass on both versions.
How to see it
- Read every loop bound out loud with the smallest and largest legal input in mind: zero elements, one element, and exactly the boundary.
- For any index arithmetic, ask which of
lengthandlength - 1is a count and which is a position — and check the answer is the same on both sides of the expression. - Treat a changed comparison operator in a diff as a change of behaviour, not of style.
A minimal pair
Correct
for (let i = 0; i < items.length; i += 1) {
total += items[i];
}
Defective
for (let i = 0; i <= items.length; i += 1) {
total += items[i];
}
Identical on an empty list. On any non-empty list the last pass adds undefined, and the total becomes NaN.
Practise it
3 diffs in the corpus carry this class. They are not listed, because knowing which diff contains what would make finding it a comprehension question about this page.
Go to the exercises