CodeRaptor
Back to Logic Errors
High Severity

Off-by-One Errors

Off-by-one errors occur when a loop iterates one time too many or too few, or when array/string indices are incorrectly calculated. These subtle bugs are among the most common programming mistakes.

Also known as: Fencepost errors, OBOE (Off-By-One Error), or the "fence post problem" - if you need to build a fence with 10 sections, you need 11 posts, not 10.

What Is an Off-by-One Error?

An off-by-one error (OBOE) is a logic error where a boundary condition is violated by being off by one unit. This typically happens in:

Loop iterations (one too many or too few)
Array indexing (accessing wrong element)
String operations (incorrect substring bounds)
Range conditions (using ≤ instead of <, or vice versa)
Buffer allocations (allocating wrong size)
Pointer arithmetic (moving one position off)

Common Examples

Loop Boundary Error

Bad - Off by One
// Accessing array[5] when length is 5 (0-4 valid)
const arr = [1, 2, 3, 4, 5];
for (let i = 0; i <= arr.length; i++) {  // Wrong: <= includes arr.length
  console.log(arr[i]);  // Error: arr[5] is undefined
}
Good - Correct Boundary
const arr = [1, 2, 3, 4, 5];
for (let i = 0; i < arr.length; i++) {  // Correct: < excludes arr.length
  console.log(arr[i]);  // Accesses arr[0] through arr[4]
}

// Or better yet, use modern iteration
arr.forEach(item => console.log(item));

String Substring Error

Bad - Incorrect Bounds
const str = "Hello";
// Trying to get last 2 chars: "lo"
const result = str.substring(str.length - 2, str.length + 1);  // Wrong: length + 1 exceeds bounds
// JavaScript silently adjusts, but in other languages this crashes
Good - Correct Bounds
const str = "Hello";
// Get last 2 chars
const result = str.substring(str.length - 2);  // Correct: omit end param
// Or
const result2 = str.slice(-2);  // Even better: negative index
console.log(result);  // "lo"

Range Check Error

Bad - Wrong Comparison
// Checking if value is in range [0, 100)
function isInRange(value) {
  return value >= 0 && value <= 100;  // Wrong: includes 100
}

isInRange(100);  // Returns true, but 100 should be excluded
Good - Correct Comparison
// Checking if value is in range [0, 100)
function isInRange(value) {
  return value >= 0 && value < 100;  // Correct: excludes 100
}

isInRange(100);  // Returns false, as expected

Language-Specific Pitfalls

Python - List Slicing

Common Mistake
# Get last 3 elements - WRONG
arr = [1, 2, 3, 4, 5]
last_three = arr[len(arr)-3:len(arr)+1]  # Off by one!

# Range iteration - WRONG
for i in range(1, len(arr)):  # Misses last element
    print(arr[i])
Correct Approach
# Get last 3 elements - CORRECT
arr = [1, 2, 3, 4, 5]
last_three = arr[-3:]  # Python negative indexing

# Range iteration - CORRECT
for i in range(len(arr)):  # Includes all elements
    print(arr[i])

# Better: use direct iteration
for item in arr:
    print(item)

C/C++ - Buffer Overflow Risk

Dangerous - Buffer Overflow
char buffer[10];
// Copy string - WRONG
for (int i = 0; i <= strlen(src); i++) {  // Off by one!
    buffer[i] = src[i];  // Writes past buffer end
}

// Allocate array - WRONG
int* arr = malloc(10 * sizeof(int));
for (int i = 0; i <= 10; i++) {  // Accesses arr[10]!
    arr[i] = i;
}
Safe - Correct Boundaries
char buffer[10];
// Copy string - CORRECT
for (int i = 0; i < strlen(src) && i < 9; i++) {
    buffer[i] = src[i];
}
buffer[9] = '\0';  // Null terminate

// Or better: use strncpy
strncpy(buffer, src, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';

// Allocate array - CORRECT
int* arr = malloc(10 * sizeof(int));
for (int i = 0; i < 10; i++) {  // Correct boundary
    arr[i] = i;
}

Java - Substring and Loop Bounds

IndexOutOfBoundsException
String str = "Hello";
// Get last char - WRONG
char last = str.charAt(str.length());  // Index 5 doesn't exist!

// Substring - WRONG
String sub = str.substring(0, str.length() + 1);  // Past end!

// Array iteration - WRONG
int[] arr = {1, 2, 3, 4, 5};
for (int i = 1; i <= arr.length; i++) {  // Accesses arr[5]!
    System.out.println(arr[i]);
}
Correct Indexing
String str = "Hello";
// Get last char - CORRECT
char last = str.charAt(str.length() - 1);  // Index 4

// Substring - CORRECT
String sub = str.substring(0, str.length());  // Or just str

// Array iteration - CORRECT
int[] arr = {1, 2, 3, 4, 5};
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

// Better: enhanced for loop
for (int num : arr) {
    System.out.println(num);
}

Real-World Scenarios

Pagination Logic

Calculating page counts and indices

// WRONG: excludes last item
const pages = Math.floor(total / pageSize);

// CORRECT: ceil for partial pages
const pages = Math.ceil(total / pageSize);

// WRONG: skips first item on page 2
const offset = page * pageSize;

// CORRECT: 0-indexed pages
const offset = (page - 1) * pageSize;

Date Calculations

Month boundaries and day counting

// WRONG: months are 0-indexed
const date = new Date(2024, 1, 31);
// Creates March 2nd, not Feb 31!

// CORRECT: account for 0-indexing
const date = new Date(2024, 1, 28);

// WRONG: doesn't include end date
const days = endDate - startDate;

// CORRECT: inclusive range
const days = endDate - startDate + 1;

Binary Search

Midpoint calculation in search algorithms

// WRONG: infinite loop risk
while (left <= right) {
  mid = (left + right) / 2;
  if (arr[mid] === target) return mid;
  if (arr[mid] < target) left = mid;
  else right = mid;  // Never moves!
}

// CORRECT: moves boundaries
while (left <= right) {
  mid = Math.floor((left + right) / 2);
  if (arr[mid] === target) return mid;
  if (arr[mid] < target) left = mid + 1;
  else right = mid - 1;
}

Grid/Matrix Operations

2D array boundary checks

// WRONG: accesses out of bounds
for (let i = 0; i <= grid.length; i++) {
  for (let j = 0; j <= grid[i].length; j++) {
    process(grid[i][j]);  // CRASH
  }
}

// CORRECT: proper boundaries
for (let i = 0; i < grid.length; i++) {
  for (let j = 0; j < grid[i].length; j++) {
    process(grid[i][j]);
  }
}

File Processing

Reading lines or bytes from files

// WRONG: reads one too many
const lines = file.split('\n');
for (let i = 0; i <= lines.length; i++) {
  processLine(lines[i]);  // undefined
}

// CORRECT: proper loop bound
const lines = file.split('\n');
for (let i = 0; i < lines.length; i++) {
  processLine(lines[i]);
}

Circular Buffers

Ring buffer index wrapping

// WRONG: off-by-one on wrap
nextIndex = (index + 1) % (size + 1);
// Skips position when size=10

// CORRECT: proper modulo
nextIndex = (index + 1) % size;

// WRONG: doesn't handle full buffer
if (count === size) throw 'Full';

// CORRECT: proper full check
if ((tail + 1) % size === head)
  throw 'Full';

How to Prevent Off-by-One Errors

Use Modern Iteration

Prefer forEach, map, for...of instead of index-based loops

arr.forEach(item => {...})

Test Boundaries

Always test with empty arrays, single elements, and edge cases

test([]), test([1]), test([1,2])

Use Length Properties

Use array.length instead of hardcoded values

i < arr.length

Inclusive vs Exclusive

Be clear about whether ranges include boundaries

[start, end) vs [start, end]

Use Slice/Substring

Use built-in methods that handle bounds correctly

arr.slice(0, 5)

Write Unit Tests

Test edge cases: first element, last element, empty collection

expect(fn([1])).toBe(...)

Impact on Applications

Buffer Overflows & Security Vulnerabilities

In languages like C/C++, off-by-one errors can write past buffer boundaries, leading to memory corruption, crashes, or exploitable security vulnerabilities (CVEs).

Security impact:

The Heartbleed bug (CVE-2014-0160) was partially caused by incorrect boundary checks, allowing attackers to read sensitive data from server memory.

Data Processing Errors

Skipping the first or last element of a dataset can lead to incorrect calculations, missing records in reports, or incomplete data migrations.

Real-world example:

An analytics dashboard displays 99 records instead of 100 because a pagination query uses <= instead of <, causing the last record to appear on a non-existent next page.

UI/UX Bugs

Off-by-one errors in UI code can cause visual glitches, truncated text, missing list items, or pagination that skips content.

Real-world example:

An infinite scroll feature loads items 11-20 when the user reaches item 10, skipping item 10 itself and creating a confusing user experience.

Detection Methods

Static Analysis

  • Linters that flag suspicious loop conditions (<= with array.length)
  • Static analyzers like Coverity or PVS-Studio for C/C++
  • ESLint rules for array access patterns
  • AI-powered tools like CodeRaptor that understand context

Testing Strategies

  • Boundary value testing (empty, 1 element, many elements)
  • Property-based testing to explore edge cases automatically
  • Memory sanitizers (AddressSanitizer, Valgrind) for C/C++
  • Assertions to verify loop invariants and postconditions

How CodeRaptor Detects Off-by-One Errors

CodeRaptor analyzes loop bounds, array accesses, and range conditions to identify potential off-by-one errors before they cause bugs.

Flags suspicious <= in array loops
Detects array access at length boundary
Identifies incorrect range comparisons
Try CodeRaptor Free