Off-by-One Errors: Count Twice Loop Once

Off-by-One errors are often simple syntactic slipups that can cause great headaches in debugging. In this article we look as some common cases, some edge cases, and quick tips about avoiding OBOE's
off by one error alpharithms

An Off-By-One Error (OBOE) is common among any system in which discrete elements are found. In programming and computer science, these errors pop up often during loops and memory allocation. Off-by-one errors happen when there are n-many elements expected and either n+1 or n-1 elements are present.

Termination conditions set using <= instead of < and iterators initialized to non-zero values are two common causes of OBOE’s. Lower-level languages like C — those requiring manual memory management — also subject developers to OBOE cases where terminators, EOF, and similar characters are automatically appended after certain operations.

Example: Off-By-One For Loop

Languages like Python make it easier to avoid off-by-0ne errors through easy reference-based loops. Older iterator-based syntaxes — where static termination conditions are defined — leave much more responsibility in the hands of the developer. Consider the following loops written in JavaScript:

// define a value for n
let n = 5;

// A: loops n-many times
for (let i = 0; i < n; i++){}

// B: loops n-1 times
for (let i = 1; i < n; i++){}

// C: loops n+1 times
for (let i = 0; i <= n; i++){}

n-times

In the first loop A we see the value of i incremented until it is of value n-1 such that a value of n terminates the loop. This produces a loop of n values because our iterator i is initialized to a starting value of 0 such that 0, 1, 2, 3, 4 are 5 unique values, equal to the value assigned to n.

n-1 times

In the second loop B we see the value of i incremented until it is of value n-1 as well. However, in this case, i was initialized with a value of 0 such that the only valid values are 1, 2, 3, 4 or 4 total — 1 fewer than the value 5 which is assigned to n.

n+1 times

In the third loop C, we see the value of i incremented until it is <= the value of n which, in this case, will continue the loop until i reaches a value of 6. This includes the values 0, 1, 2, 3, 4, 5 given i was initialized to 0 such that there are a total of 6 or n+1 elements given the value of n is 6.

Example: Index Errors

In cases where one accesses elements of an array iteratively OBOE’s result from using the <= operator or initializing a iterator to a non-zero value. Depending on the language, these issues may or may not cause an issue. Let’s take a look at an example in Python:

# define a list of 6 total values
n = [0, 1, 2, 3, 4, 5]

# Define an iterator variable
i = 0
while i <= len(n):

    # print the value of i
    print("i = ", i)

    # print the value of the ith element of n
    print("n[i] = ", n[i])

    # increment the iterator
    i += 1

Here we have a list of 6 values and enter a while loop that will access each element via the iterator variable i which gets incremented each iteration via the i += 1 segment. The printout is as follows:

i =  0
n[i] =  0
i =  1
n[i] =  1
i =  2
n[i] =  2
i =  3
n[i] =  3
i =  4
n[i] =  4
i =  5
n[i] =  5
i =  6
Traceback (most recent call last):
  File "C:/Users/pc/Desktop/mercator/scatch.py", line 13, in <module>
    print("n[i] = ", n[i])
IndexError: list index out of range

Here we see no issue in incrementing the value of i to 6 but when we try to access the 6th element of n we cause a IndexError exception — keep in mind our list is 0-indexed. This code tries to access an element of n that doesn’t exist and Python throws an exception as a result. Let’s consider a similar example using JavaScript:

// Create an array of 6 elements
let n = [0, 1, 2, 3, 4, 5]

// iterate until i <= 6
for (let i = 0; i <= n.length; i++){
    console.log("i = ", i);
    console.log("i = ", n[i]);
}

The logic is identical to the above Python code but written here in JavaScript syntax. We have an array n with 6 items and we loop until our iterator i‘s value is greater than 6. This produces the following output to the console:

i =  0
n[i] =  0        
i =  1           
n[i] =  1        
i =  2           
n[i] =  2        
i =  3           
n[i] =  3        
i =  4           
n[i] =  4        
i =  5           
n[i] =  5        
i =  6           
n[i] =  undefined

The last line where the n[i] syntax was used attempts to access an element of n that doesn’t exist. JavaScript doesn’t throw an exception here however and simply returns an undefined object. This can be useful in cases where crashes should be avoided but also infuriating in cases where one would expect such a logical error to cause an issue.

Common Causes

In languages like C, there are certain functions like strcat in which the first n characters of a source string are used to create a second string. However, this function also appends a terminator character after the operation such that there must be n+1 bytes available in a buffer allocated for the copy operation. Aside from such less common issues, the following checklist is a good guide for avoiding off-by-one errors:

  • Iterators that are initialized to non-zero values
  • Termination conditions that use == or <= operators

Final Thoughts

Off-By-One errors are often products of simple typos but can also reflect larger logical flaws. Loop conditions and iterator initialization values are common causes but certainly not exclusive sources of OBOE’s. OBOE’s aren’t exclusive to computer programming and can appear in general calculations as well.

Consider the case of creating a fence that spans 25ft such that there is a fencepost every foot. How many total fence posts would be needed? An easy mistake is to answer 25 posts — there will be 25 posts needed for each 1ft span but there will also be an initial post needed to start. This would bring the total to 25 + 1 post!

Zαck West
BSc Graphic Comm. NSCU, BSc CS Candidate WCU. Life-long learner and entrepreneur specializing in design, digital marketing, and web app development. Fascinated by natural systems, concurrency, and the nature of consciousness.