What Is an Off-by-One Error? Ranges, Remainders, and Indexes

An off-by-one error is a boundary that is one step away from the index you meant. Empty ranges, a zero remainder, and a match you fail to exclude are the usual places it shows up.

An off-by-one error uses a boundary that is one step away from the slot you meant. The value is often almost right, which is why the tests that sit on the edge are the ones that catch it.

How it works

Arrays start at index 0 and the last index is length - 1. A loop that runs while index <= length reads one past the end. A slice that ends at length - 1 when you meant length drops the last value.

Rotation uses a remainder. steps % length is 0 when the move is a full turn. Treating 0 as "move one" rotates an array that should have stayed put. An empty array has length 0, and the remainder is not defined in the way you want. Handle that length before you divide.

Binary search keeps an inclusive range. When the middle matches and you still need the first or last copy, set hi to mid - 1 or lo to mid + 1. Leaving the bound on mid can repeat the same middle forever.

Theme
Terminal

Press Run to execute this example. Use console.log to print a value.

Expected output: [1, 2, 3, 4], then [2, 3, 4, 1], then [4, 1, 2, 3], then []. The examples exercise the full-turn, one-past-a-full-turn, negative-step, and empty-input boundaries.

Worked example

With length 4 and 5 steps, 5 % 4 is 1. The half-open slices are nums.slice(1) = [2, 3, 4] and nums.slice(0, 1) = [1]; joining them gives [2, 3, 4, 1].

Why it is correct

The normalized remainder r is always in [0, length). Every original index from r onward appears first, in order, and every index before r appears afterward, also in order. The two half-open slices are disjoint and together contain every input index exactly once, so the result is precisely a left rotation by r.

Cost

Computing the boundary is O(1). Slicing and concatenating copy n values, so the operation is O(n) time and O(n) extra space. The early return avoids taking a remainder by zero for an empty array.

In the course

Arrays warn you when a left rotation's remainder is 0. Binary search states the inclusive range out loud, and the following lesson keeps searching after a match. The empty range, lo > hi, is the stop, not a special case you bolt on at the end.

Common mistakes

Using < when the last index should be visited, or <= when it should not.

Forgetting that a full turn is a remainder of 0, not a remainder of 1.

Updating a binary-search bound to mid instead of mid - 1 or mid + 1, so the range never shrinks.

When to use it

Check the empty input, the single element, the first index, and the last index before you trust a loop. Those four cases are where an off-by-one lives.

When not to

Do not "fix" a wrong answer by adding or subtracting one until a single example passes. Name the range, then move the bound so the range still means what you said.

Problem-solving checklist

Write whether each boundary is inclusive or exclusive. Trace the empty and one-element cases on paper. For a shrinking search range, prove that every update removes at least one index. Test the first valid index, the last valid index, and the first invalid index.

Optional enrichment

Implement sliceInclusive(values, start, end) without mutating the input. Reject invalid bounds explicitly, and test the difference between an empty half-open range and a one-element inclusive range.