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

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.