R Error: subscript out of bounds — What It Means & How to Solve It
Error in x[[i]] : subscript out of bounds means you tried to access an element at a position that doesn't exist. The index is either too large, negative in the wrong way, or NA. Here's how to diagnose and fix it.
When Does This Error Occur?
This error happens with [[ and matrices/arrays, but not with [ on vectors (which returns NA instead):
x <- c(10, 20, 30)
# Single bracket: returns NA (no error)
cat("x[5] with [:", x[5], "\n")
# Double bracket: throws an error
tryCatch(
x[[5]],
error = function(e) cat("x[[5]] with [[:", conditionMessage(e), "\n")
)
# Same for lists
my_list <- list(a = 1, b = 2)
tryCatch(
my_list[[3]],
error = function(e) cat("list[[3]]:", conditionMessage(e), "\n")
)
Cause 1: Index Exceeds Length
The most common cause — you're asking for element 10 of a 5-element vector:
scores <- c(88, 92, 75, 81, 96)
cat("Length:", length(scores), "\n")
# Safe: check length first
i <- 8
if (i <= length(scores)) {
cat("Score:", scores[[i]], "\n")
} else {
cat("Index", i, "is out of bounds (max:", length(scores), ")\n")
}
# Safer: use a helper
safe_get <- function(x, i, default = NA) {
if (i >= 1 && i <= length(x)) x[[i]] else default
}
cat("safe_get(scores, 3):", safe_get(scores, 3), "\n")
cat("safe_get(scores, 8):", safe_get(scores, 8), "\n")
Cause 2: Off-by-One in Loops
A very common bug — looping one past the end:
values <- c(5, 10, 15, 20)
# BUG: 1:length(values)+1 is NOT 1:(length(values)+1)
# It's (1:length(values)) + 1 = c(2, 3, 4, 5)
# So values[[5]] is out of bounds!
# Fix: use seq_along() or seq_len()
for (i in seq_along(values)) {
cat(sprintf("values[[%d]] = %d\n", i, values[[i]]))
}
# Also common: comparing adjacent elements
for (i in seq_len(length(values) - 1)) {
diff <- values[[i + 1]] - values[[i]]
cat(sprintf("Diff %d->%d: %d\n", i, i+1, diff))
}
Cause 3: NA as Index
If your index variable is NA, you get an error with [[:
# NA index with [[
idx <- NA
x <- list(a = 1, b = 2, c = 3)
tryCatch(
x[[idx]],
error = function(e) cat("NA index error:", conditionMessage(e), "\n")
)
# Fix: check for NA before indexing
if (!is.na(idx) && idx >= 1 && idx <= length(x)) {
cat("Value:", x[[idx]], "\n")
} else {
cat("Invalid index:", idx, "\n")
}
Cause 4: Empty Subset Result
Using which() or filtering that returns no results, then indexing:
data <- c(10, 20, 30, 40, 50)
# which() returns integer(0) when nothing matches
idx <- which(data > 100)
cat("Matching indices:", idx, "\n")
cat("Length:", length(idx), "\n")
# This is fine with [:
cat("data[idx]:", data[idx], "\n") # Returns numeric(0)
# But this would error with [[:
if (length(idx) > 0) {
cat("First match:", data[[idx[1]]], "\n")
} else {
cat("No matches found\n")
}
# Pattern 1: Bounds checking helper
check_bounds <- function(x, i) {
n <- if (is.null(dim(x))) length(x) else nrow(x)
if (any(is.na(i))) stop("NA in index")
if (any(i < 1) || any(i > n)) {
stop(sprintf("Index %s out of bounds [1, %d]",
paste(i[i < 1 | i > n], collapse=","), n))
}
TRUE
}
x <- 1:5
tryCatch(
{ check_bounds(x, 7); x[[7]] },
error = function(e) cat("Caught:", conditionMessage(e), "\n")
)
# Pattern 2: Use seq_along() in loops
x <- c(3, 1, 4, 1, 5)
for (i in seq_along(x)) {
cat(x[[i]], " ")
}
cat("\n")
# Pattern 3: Defensive list access
safe_list_get <- function(lst, key, default = NULL) {
if (key %in% names(lst)) lst[[key]] else default
}
config <- list(host = "localhost", port = 8080)
cat("host:", safe_list_get(config, "host"), "\n")
cat("timeout:", safe_list_get(config, "timeout", 30), "\n")
Practice Exercise
# Exercise: This function crashes with "subscript out of bounds"
# for certain inputs. Find the bug and fix it.
find_pairs <- function(x, target) {
pairs <- list()
for (i in 1:length(x)) {
for (j in (i+1):length(x)) {
if (x[[i]] + x[[j]] == target) {
pairs[[length(pairs) + 1]] <- c(i, j)
}
}
}
pairs
}
# Works:
cat("Pairs summing to 10:\n")
result <- find_pairs(c(3, 7, 5, 5, 2, 8), 10)
for (p in result) cat(" indices", p[1], "and", p[2], "\n")
# Crashes with single element:
# find_pairs(c(5), 10)
# Fix the function below:
Click to reveal solution
```r
# Bug: When x has 1 element, (i+1):length(x) becomes 2:1 = c(2,1)
# which causes x[[2]] to be out of bounds.
# Fix: check that j > i and j <= length(x)
find_pairs <- function(x, target) {
pairs <- list()
n <- length(x)
if (n < 2) return(pairs) # Need at least 2 elements
for (i in 1:(n - 1)) {
for (j in (i + 1):n) {
if (x[[i]] + x[[j]] == target) {
pairs[[length(pairs) + 1]] <- c(i, j)
}
}
}
pairs
}
# Now works for all cases:
cat("Normal:", length(find_pairs(c(3, 7, 5, 5, 2, 8), 10)), "pairs\n")
cat("Single:", length(find_pairs(c(5), 10)), "pairs\n")
cat("Empty:", length(find_pairs(c(), 10)), "pairs\n")
**Explanation:** The bug is the `1:length(x)` pattern when `x` has only 1 element: `(i+1):length(x)` becomes `2:1`, which is `c(2, 1)`. Guard with `if (n < 2) return()` and use `1:(n-1)` for the outer loop.
Summary
Cause
Example
Fix
Index too large
x[[10]] on length-5
Check length(x) first
Off-by-one in loop
1:(length(x)+1)
Use seq_along(x)
NA index
x[[NA]]
Check !is.na(i)
Empty which()
x[[which(x>999)[1]]]
Check length() of result
Matrix bounds
mat[5,2] on 3-row
Check nrow()/ncol()
FAQ
Why does x[10] return NA but x[[10]] throws an error?
[ is designed for subsetting (can return multiple elements) and uses NA as a placeholder for missing positions. [[ is designed for extracting a single element and is stricter — there's no meaningful "single NA element" to return.
How do I handle this in lapply/sapply?
If your function might get indices out of range, wrap the access in tryCatch or validate before accessing. purrr::pluck() is a safe alternative that returns a default value when the element doesn't exist.
What's Next?
R Error: undefined columns selected — data frame subsetting fix
R Error: replacement has length zero — the NA assignment bug
R Common Errors — the full reference of 50 common errors