Fix Common Errors

Fix NumPy IndexError: Index Out of Bounds for Axis

Fix "IndexError: index N is out of bounds for axis M with size K" in NumPy — what the axis numbers mean, why the shape isn't what you think, and how to guard it.

IndexError: index 1 is out of bounds for axis 1 with size 1 is NumPy's version of "list index out of range" — but with a twist that makes it more confusing and more useful at the same time: it tells you which dimension you overflowed. The error means you asked for position 1 along axis 1 (the columns), but that axis only has 1 element (so the only valid index is 0). Nearly every occurrence comes down to one thing: the array's shape isn't what you think it is. This guide decodes the message, shows the handful of code patterns that produce it, and gives you the checks that prevent it.

What the error message is telling you

NumPy arrays are indexed per axis: axis 0 is rows, axis 1 is columns, axis 2 the next dimension, and so on. Read the message as three facts:

import numpy as np

a = np.array([[10], [20], [30]])   # shape (3, 1) — 3 rows, 1 column
a[0, 1]
# IndexError: index 1 is out of bounds for axis 1 with size 1
  • index 1 — the index you asked for,
  • axis 1 — the dimension you asked it on (columns here),
  • size 1 — how many elements that axis actually has, so valid indices are 0 only (or -1).

The first debugging move is always the same — print the shape:

print(a.shape)   # (3, 1)  ← not the (3, 2) you assumed
print(a.ndim)    # 2

Remember that indexing is zero-based and the upper bound is exclusive: an axis of size n accepts indices 0 … n-1 (and negative indices -n … -1). "Size 1" in the message means only 0 works.

The usual suspects

A slice or squeeze changed the shape upstream. Slicing with an integer removes an axis; slicing with a range keeps it. Code that assumes 2-D breaks the moment a single row comes back as 1-D:

m = np.arange(12).reshape(3, 4)

row = m[0]        # shape (4,)   — axis 0 is gone, columns are now axis 0!
row2 = m[0:1]     # shape (1, 4) — still 2-D

row[0, 2]         # IndexError / too many indices — row is 1-D now

The data loaded differently than you expected. A CSV with a single column, np.loadtxt collapsing one-row files to 1-D, or a dataframe column extracted as (n, 1) instead of (n,). Your indexing code is fine — the input isn't. This is the classic case of the production data violating an assumption the test data never did, the same category of surprise as most ValueError shape mismatches.

Transposed logic: right numbers, wrong axes. a[row, col] vs a[col, row]. With a (3, 1) array, a[1, 0] is fine and a[0, 1] explodes — if the message says axis 1 but you were sure you were indexing rows, your axes are swapped.

A loop bound taken from the wrong axis:

for j in range(len(m)):     # len(m) is m.shape[0] — the ROW count
    process(m[0, j])        # but j is used as a COLUMN index

len(m) on a 2-D array is shape[0], not the column count. Use the axis you mean: for j in range(m.shape[1]).

An index computed elsewhere. np.argmax on a flattened array used against a 2-D one, an index from a different (longer) array, or an off-by-one from searchsorted. The index is valid somewhere — just not on this array.

How to fix it

  1. Assert the shape at the boundary. Wherever data enters your function, make the assumption explicit — you convert a confusing crash deep in the indexing code into an immediate, readable failure:
def score(features: np.ndarray) -> np.ndarray:
    assert features.ndim == 2 and features.shape[1] == 4, features.shape
    return features[:, 1] * 0.5 + features[:, 3]
  1. Normalize the shape instead of guessing. np.atleast_2d, reshape(-1, n), or an explicit squeeze()/expand_dims at load time gives every downstream line one shape to handle.

  2. Index with the tools that respect axes. np.take(a, idx, axis=1) makes the axis explicit; boolean masks (a[:, mask]) can't overflow; and negative indices (a[:, -1]) beat a[:, a.shape[1] - 1].

  3. Validate computed indices before using them, exactly as you would guard a Python list index: if 0 <= j < a.shape[1]: — or better, ask why an out-of-range index was produced at all.

Don't "fix" this with a bare try/except IndexError: pass. Swallowing the error keeps the wrong-shaped data flowing into whatever comes next — usually a subtler numerical bug that no exception will ever flag.

When it happens in production

In a notebook you just print a.shape and move on. In a deployed service — a model endpoint, a Celery pipeline, a nightly batch job — the interesting question is what input produced the bad shape, and by the time you look, the input is gone. An error tracker for Python captures the exception with its full stack trace and local context (including the shape values you log alongside it), groups the thousands of repeats into one issue, and tells you which job or request triggered it. From there the workflow is the standard one for debugging production errors: reproduce with the captured input shape, add the boundary assert, ship.

import sentry_sdk
sentry_sdk.init(dsn="https://<key>@light-trace.robomiri.com/1")

Any Sentry-compatible SDK works with LightTrace by changing the DSN — every IndexError after that arrives grouped, with the traceback and context that a log line loses.

Start tracking errors in minutes

Chasing a shape bug you can't reproduce? LightTrace captures every production IndexError with the full traceback and context, so you can see the input that broke it.

Fix your next production error faster

Point any Sentry SDK at LightTrace — free up to 5,000 events/month.