Using Julia’s @inbounds Macro to Eliminate Array Bounds Checks
Learn how @inbounds removes runtime bounds checks in tight loops, when it is safe to apply, and what pitfalls to avoid.
04 Sept 2026, 04:03 UTC

When to use @inbounds for faster loops
The useful answer is simple: if you can prove that every array index inside a loop stays within the array’s valid range, wrapping that loop with @inbounds tells the Julia compiler to skip runtime bounds checks. This can tighten the inner loop and give a 10‑30% speedup for numerical kernels that are memory‑bound or have very lightweight bodies.
Worked example: sum of squares
Consider a function that computes the sum of squares of a one‑dimensional Array{Float64,1}. The naive version performs a bounds check on each access:
function sum_square_no_check(a)
s = 0.0
for i in 1:length(a)
s += a[i] * a[i]
end
return s
end
Adding @inbounds removes those checks:
function sum_square_with_check(a)
s = 0.0
@inbounds for i in 1:length(a)
s += a[i] * a[i]
end
return s
end
To see the effect on the generated code, you can inspect the LLVM intermediate representation (IR) inside a Julia session:
using LLVM
@code_llvm sum_square_no_check((Float64,))
@code_llvm sum_square_with_check((Float64,))
In the IR for the version with @inbounds you will notice that the call @jl_array_ref_bounds_check (or similar) instruction is absent from the loop body, while it appears in the version without the macro.
Limits and safety considerations
- Index guarantee required. The programmer must ensure that
inever goes below 1 or abovelength(a). If the guarantee is violated, the program may read or write invalid memory, leading to silent data corruption or a segmentation fault. - Array mutation inside the loop. If the array is resized, reshaped, or its underlying data pointer changes while the loop runs, the previously verified bounds may no longer hold. Avoid using
@inboundswhen the array could be altered by another task or by code inside the loop. - Debugging and REPL mode. The macro only affects code that is compiled for execution; interpreted code in the REPL still performs bounds checks. Therefore, you may not see a performance difference when testing small snippets interactively.
- Nested loops. Applying
@inboundsto an outer loop does not automatically protect inner loops; each loop that accesses arrays must be annotated separately if you want to skip its checks.
Common mistakes
- Assuming safety from loop limits. Writing
for i in 1:length(a)looks safe, but ifais a view or a subarray with an offset, the effective indices may differ. Always check the actual indices used. - Using @inbounds on conditional accesses. If the index is computed from a variable that could be out of range under some branch, the macro will still skip the check and cause undefined behavior.
- Over‑applying the macro. Wrapping large sections of code that contain unrelated operations (e.g., I/O, type conversions) yields little benefit and increases the risk of missing a bounds violation.
Practical verification steps
To confirm that @inbounds is having the intended effect, you can:
- Benchmark. Generate a large
Float64vector and time both versions withBenchmarkTools.@btimeor the built‑in@time. Look for a consistent reduction in elapsed time. - Introduce a deliberate out‑of‑bounds access. Temporarily replace the loop body with something like
a[length(a)+1] = 0inside an@inboundsblock. The program should crash or throw a memory‑related error, confirming that bounds checks are suppressed. - Inspect LLVM IR. As shown above, compare the IR with and without the macro to see the removal of the bounds‑check call.
When you have verified that the indices are provably safe and that the array is not mutated inside the loop, @inbounds is a reliable way to squeeze extra performance out of tight numerical loops in Julia.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.