BugHunt

Off-by-one errors in Python and JavaScript

The loop that runs one time too many, the slice that drops the last item, the index that starts in the wrong place.

13 free challenges7 Python6 JavaScript

What is off-by-one error?

An off-by-one error is a loop bound, index or count that is wrong by exactly one. The logic is right; the arithmetic is one step out. They are among the most common bugs in programming precisely because they hide so well — the code produces almost the right answer, which is far harder to notice than no answer at all.

Why it happens

Almost every off-by-one comes from mixing two counting conventions in the same expression. Array indices start at 0 but lengths start at 1. Python's range() and JavaScript's slice() exclude their upper bound, while a phrase like "items 1 to 10" includes both ends. Whenever those conventions meet without a deliberate conversion, something ends up one out.

How to recognise it

  • The result is right except for the first or last element.
  • A sum comes out as NaN in JavaScript, because reading past the end gave undefined.
  • IndexError in Python at exactly the final iteration.
  • It works for even-sized input and breaks for odd, or vice versa.

Errors and symptoms this causes

  • IndexError: list index out of range
  • IndexError: string index out of range
  • Cannot read properties of undefined
  • loop runs one extra time
  • last item missing from array

How to fix it

Test the smallest case by hand. A list of length n has indices 0 to n-1; a window of size k fits n - k + 1 times; n items need n - 1 separators. Write the boundary case as a test before you write the loop, because the boundary is exactly where these live and exactly what casual testing skips.

Practise off-by-one errors

Working code with one bug in it. Find it, fix it in the browser, and see the explanation. No account needed.

javascriptEasy
Capitalise repeats the first letter

capitalize(word) should upper-case the first letter, so "debug" becomes "Debug".

Off-by-one

pythonEasy
Last character index is out of range

last_char(text) should return the final character, so last_char("hello") returns "o".

Off-by-one

javascriptEasy
First chunk drops its last item

firstChunk(arr, size) should return the first `size` items of an array, so firstChunk([1,2,3,4,5], 3) gives [1,2,3].

Off-by-one

pythonEasy
Off-by-one in range sum

sum_range(n) should return the sum of all integers from 1 to n, inclusive.

Off-by-one

javascriptEasy
Loop runs one step past the end

sumAll(nums) should add up every number in the array, so sumAll([1, 2, 3]) returns 6.

Off-by-one

pythonHard
Sliding window never reaches the end

max_window_sum(nums, k) should return the largest sum of any k consecutive numbers. max_window_sum([1,2,3,10], 2) is 13.

Off-by-one

javascriptHard
Page 1 shows the second page of results

pageItems(items, page, size) returns one page of results. Pages are numbered from 1, so pageItems([1,2,3,4,5,6], 1, 3) gives [1,2,3].

Off-by-one

pythonHard
Binary search misses the last element

binary_search(nums, target) should return the index of target in a sorted list, or -1 if it isn't there.

Off-by-one

pythonHard
Chunking silently drops the last group

chunk(items, size) should split a list into groups of size, keeping any short final group. chunk([1,2,3,4,5], 2) gives [[1,2],[3,4],[5]].

Off-by-one

pythonMedium
Range check excludes its own lower bound

count_in_range(nums, low, high) counts values between low and high INCLUSIVE, so count_in_range([1,2,3], 1, 3) returns 3.

Off-by-one

pythonMedium
Last N characters comes back one too long

last_n_chars(text, n) should return the last n characters of a string, so last_n_chars("debugging", 3) gives "ing".

Off-by-one

javascriptMedium
Off-by-one when slicing the last N items

getLastNItems(arr, n) should return the last n items of arr, e.g. getLastNItems([1,2,3,4,5], 2) -> [4, 5].

Off-by-one

javascriptMedium
Joined string ends with a stray dash

joinWithDash(words) should join words with dashes between them, so joinWithDash(["a","b"]) returns "a-b".

Off-by-one

Other bug patterns