DEV Community

Alex Hunter
Alex Hunter

Posted on • Originally published at leetcopilot.dev

Why You Keep Getting 'Index Out of Bounds' (And How to Stop It Forever)

Originally published on LeetCopilot Blog


Is it < or <=? Stop guessing. Here is the systematic 'Boundary Check' technique to eliminate off-by-one errors for good.

You've written the solution. The logic looks right. You submit—and it fails on one test case. You check the output: you're missing the first element. Or the last. Or you're accessing an index that doesn't exist.

Welcome to the off-by-one error (OBOE), the most common and frustrating bug in array manipulation. It's not that you don't understand arrays—it's that boundaries are subtle, and it only takes one <= instead of < to break everything.

This guide gives you a systematic approach to debug off-by-one errors when they happen, and coding habits to prevent them before they start.

TL;DR

  • Off-by-one errors occur when loops run one iteration too many or too few, or when array indices are off by exactly one position.
  • They're common because of zero-based indexing, inclusive vs. exclusive range confusion, and boundary misunderstandings.
  • Debugging techniques: reproduce the bug with minimal input, use print statements to track indices and values, manually trace with edge cases.
  • Prevention strategies: use < length not <= length-1, test empty arrays and single elements first, prefer language-native iteration when possible.
  • You'll learn how to identify symptoms, isolate the bug, and build habits that eliminate 90% of these errors before they happen.

Beginner-Friendly Explanations

What is an Off-by-One Error?

An off-by-one error is a logic mistake where your code iterates or accesses one element too many or too few. Classic examples:

  • Loop runs n+1 times when it should run n times
  • Accessing array[length] when the last valid index is array[length - 1]
  • Slicing array[0:n-1] when you meant array[0:n]

These bugs are "off by one" because the error margin is exactly one iteration or one index.

Why They're So Common

Three main culprits:

  1. Zero-based indexing: Arrays start at 0, so an array of length 5 has indices 0–4. Forgetting this causes array[5] to be out of bounds.
  2. Inclusive vs. exclusive ranges: Python's array[0:3] includes indices 0, 1, 2 but not 3. Mixing up inclusive/exclusive semantics is a top mistake.
  3. Boundary conditions: Edge cases like empty arrays, single elements, or full-length iterations expose off-by-one logic that works for typical cases.

When They Appear Most Often

  • Sliding window problems (moving left and right pointers)
  • Subarray slicing and substring extraction
  • Binary search (mid-point calculations and boundary updates)
  • Two-pointer techniques (especially when pointers should meet or cross)

Understanding the debugging process is crucial for solving array problems efficiently.


Step-by-Step Learning Guidance

1) Reproduce the Bug Consistently

Identify the exact input that triggers the error. LeetCode usually shows you the failing test case. Copy it into your local environment and confirm it fails there too.

Why this matters: You can't debug what you can't reproduce. A consistent failure lets you test fixes immediately.

2) Minimize the Input

Reduce the failing case to the smallest possible size. If [1, 2, 3, 4, 5] fails, test [1, 2] or even [1]. Smaller inputs make manual tracing feasible.

Example: If your sliding window fails on a 100-character string, find the shortest string that still breaks it—often 2–3 characters.

3) Use Print Statements to Track Indices

Place print statements inside loops to show:

  • Loop counter (i)
  • Array access (array[i])
  • Start/end pointers (left, right)
  • Calculated boundaries (mid, end)
for i in range(len(array)):
    print(f"i={i}, array[i]={array[i]}")  # Verify i stays in bounds
Enter fullscreen mode Exit fullscreen mode

What to look for: Do the printed indices match what you expect? Does i ever equal len(array) (out of bounds)?

4) Manually Trace Edge Cases

Pick edge cases and step through the code line by line on paper:

  • Empty array []
  • Single element [1]
  • Two elements [1, 2]

Write down the value of every variable at each step. This often reveals that your loop runs once when it should run zero times, or vice versa.

5) Compare Expected vs. Actual Boundaries

For loops like for i in range(start, end), ask:

  • Should end be included or excluded?
  • Is start the correct initial value?
  • If manually iterating, does i < end or i <= end match the problem requirement?

This boundary clarification is critical for understanding algorithmic constraints.


Visualizable Example: Debugging a Sliding Window OBOE

Problem: Find the maximum sum of a subarray of length k.

Buggy Code:

function maxSumSubarray(nums: number[], k: number): number {
  let maxSum = 0;
  let windowSum = 0;

  // Initial window
  for (let i = 0; i <= k; i++) {  // BUG: should be i < k
    windowSum += nums[i];
  }
  maxSum = windowSum;

  // Slide window
  for (let i = k; i < nums.length; i++) {
    windowSum += nums[i] - nums[i - k];
    maxSum = Math.max(maxSum, windowSum);
  }

  return maxSum;
}
Enter fullscreen mode Exit fullscreen mode

Test Case: nums = [1, 2, 3], k = 2

Expected Output: 5 (subarray [2, 3])

Actual Output: Crashes (out of bounds access)

Debugging Trace:

  1. Print the loop:
   for (let i = 0; i <= k; i++) {
     console.log(`i=${i}, nums[i]=${nums[i]}`);
     windowSum += nums[i];
   }
Enter fullscreen mode Exit fullscreen mode
  1. Output:
   i=0, nums[0]=1
   i=1, nums[1]=2
   i=2, nums[2]=3
   i=3, nums[3]=undefined  // Out of bounds!
Enter fullscreen mode Exit fullscreen mode
  1. Diagnosis: i <= k means i runs from 0 to 3 when k=2. But we only want indices 0 and 1 (two elements).

  2. Fix: Change i <= k to i < k:

   for (let i = 0; i < k; i++) {
     windowSum += nums[i];
   }
Enter fullscreen mode Exit fullscreen mode
  1. Verify: Now i runs 0 to 1, summing nums[0] + nums[1] = 1 + 2 = 3. The rest of the logic proceeds correctly.

Lesson: <= vs. < in loop conditions is a prime OBOE source. Always ask: "How many iterations do I need?"


Practical Preparation Strategies

Test Edge Cases First

Before submitting, run your code on:

  • [] (empty array)
  • [1] (single element)
  • [1, 2] (two elements)
  • Maximum constraint size (if feasible)

Most OBOEs surface at these boundaries.

Use < length Instead of <= length - 1

Both are mathematically equivalent, but < length is clearer:

  • for i in range(len(array)) → i goes from 0 to len(array) - 1
  • for (let i = 0; i < array.length; i++) → same

Avoid i <= array.length - 1—it's more error-prone.

Prefer Language-Native Iteration

Use for...of (JavaScript), for item in array (Python), or .forEach() when you don't need indices. This eliminates manual index management:

// Prone to OBOE
for (let i = 0; i < array.length; i++) {
  console.log(array[i]);
}

// OBOE-proof
for (const item of array) {
  console.log(item);
}
Enter fullscreen mode Exit fullscreen mode

Visualize Slicing with Concrete Numbers

When slicing, write out the indices explicitly for a small array:

  • array = [10, 20, 30, 40] (indices 0, 1, 2, 3)
  • array[0:2][10, 20] (includes 0, 1; excludes 2)
  • array[1:3][20, 30] (includes 1, 2; excludes 3)

This concrete substitution clarifies inclusive/exclusive semantics.

Use Tools to Validate Your Logic

When debugging persists, tools like LeetCopilot can highlight boundary condition mistakes by running test cases inline and showing exactly where your indices diverge from expected behavior, helping you pinpoint the error without replacing your reasoning.


Common Mistakes to Avoid

Mixing Up Inclusive and Exclusive Ends

Python slicing array[a:b] is inclusive of a, exclusive of b. But range conditions like "indices from 0 to n" sound inclusive on both ends. Clarify: "indices 0 through n-1" or "0 to n exclusive."

Forgetting Zero-Based Indexing

An array of length 5 has indices 0–4, not 1–5. When translating "first three elements," that's indices 0, 1, 2—not 1, 2, 3.

Off-by-One in Binary Search

Binary search is OBOE-prone because of mid-point rounding and boundary updates:

  • If mid = (left + right) // 2, does your update use right = mid - 1 or right = mid?
  • Test with two elements [1, 2] to ensure your search doesn't infinite-loop.

Copy-Pasting Loop Conditions

Reusing a loop structure from another problem without adjusting the boundary can silently introduce OBOEs. Always re-verify the loop range for the current problem.

Hardcoding Lengths

Avoid for i in range(5) when you mean for i in range(len(array)). If the array size changes, hardcoded values cause OBOEs.


FAQ

How do I know it's an off-by-one error and not something else?

If your output is almost correct—missing the first/last element, or crashing on index out of bounds—it's likely an OBOE. Other bugs usually produce completely wrong results.

What should I practice before this topic?

Get comfortable with zero-based indexing, understand how your language handles slicing, and practice writing loops for edge cases like empty arrays. These fundamentals prevent most OBOEs.

Is this concept important for interviews?

Absolutely. Interviewers notice if you write buggy boundary logic, even if the core algorithm is correct. Clean, OBOE-free code signals attention to detail and experience.

Should I use a debugger or print statements?

Both. Print statements are faster for quick checks, but a debugger lets you step through loops and inspect indices in real-time. Use whichever fits your workflow.

Can I avoid OBOEs entirely?

Not entirely, but you can reduce them by 90% with good habits: test edge cases early, use < length consistently, prefer native iteration, and manually trace small examples before submitting.


Conclusion

Off-by-one errors are frustrating because they're so close to correct—one character different in your loop condition, and everything works. But "close" isn't enough in coding interviews or production code.

The key to eliminating OBOEs is systematic debugging: reproduce the bug with minimal input, print indices to see exactly what's happening, manually trace edge cases, and compare your loop boundaries against the problem requirements. Pair this with preventive habits—testing empty arrays first, using < length over <= length - 1, preferring native iteration—and you'll catch these bugs before they reach submission.

When you can debug an OBOE in under two minutes by adding one print statement and testing with [1, 2], you've built the discipline that separates confident engineers from those who guess and hope. And that discipline scales: the same boundary awareness that fixes array loops also fixes pointer logic, window sliding, and binary search—all patterns worth mastering. For more debugging strategies, see how to debug off-by-one errors in LeetCode array problems and explore our sliding window tutorials for pattern-specific guidance.


If you're looking for an AI assistant to help you master LeetCode patterns and prepare for coding interviews, check out LeetCopilot.

Top comments (0)