Analytica Data Science SolutionsContact
ESC

to move to open

← All insights

Insight

Loops vs vectorization: the 40x you're leaving on the table

The same computation, written two ways, with a benchmark that isn't close. Why vectorized code wins in R, and when a loop is still right.

December 2022RPythonPerformance

Written some years ago. The ideas hold, but check package versions and API details against current documentation before relying on the code.

Here’s a benchmark from my own machine: the same arithmetic on ten million numbers, once with a loop, once vectorized.

Loop time grows linearly to about 10 seconds at ten million calculations; the vectorized line stays flat near zero.

The red line is the loop. The dashed line hugging the x-axis is vectorization doing the identical work. That’s not a tuning difference; that’s a factor of forty or more, and it’s sitting in most R scripts I get asked to look at.

A loop says do this to each element, one at a time. Vectorization says do this to the whole vector and lets the language dispatch the iteration to compiled C underneath. Same maths, different addressee:

x <- runif(1e7)

# The loop
out <- numeric(length(x))
for (i in seq_along(x)) {
  out[i] <- x[i] * 2 + 1
}

# The vectorized version
out <- x * 2 + 1

Why is the gap so violent in R specifically? Because R is interpreted, and a loop pays interpreter overhead — type checks, dispatch, bookkeeping — on every element. Ten million elements, ten million tolls. The vectorized call pays once, then runs machine code. Python has the same disease and the same cure, spelled NumPy.

Two loop pitfalls do the most damage in practice. Growing an object inside a loop (out <- c(out, ...)) reallocates the whole vector every pass and turns linear work quadratic. If you must loop, pre-allocate, as above. And the sneaky one: hiding loops inside loops, where an innocent apply call inside a for multiplies the toll.

So should you vectorize everything? No, and knowing where the boundary sits is the actual skill. A loop is right when iterations genuinely depend on each other: a simulation where step t needs step t−1, like the Collatz sequence, can’t be vectorized away. It’s right when each iteration is a heavy independent job (a model fit per file, where purrr’s map functions give you loop semantics with cleaner bones). And it’s right when clarity wins: a readable loop over a thirty-second job beats a clever one-liner nobody can maintain.

But the default posture matters. In vector languages, reach for the vectorized form first and justify the loop, not the other way round. When you genuinely need loop logic at compiled speed, that escape hatch exists too — it’s called Rcpp, and it gets its own post.

The benchmark above is one line of your time to reproduce. Run it once on your own machine. You’ll never write the naive loop again.

Discuss a project

Tell us what decision, workflow, or data problem you're working on. We'll tell you what the data you already have can support, and what a first engagement looks like.

Start a conversation