Vectorization for Speed
Replace explicit loops with vectorized operations for major speedups.
Vectorization for Speed is a free R Academy lesson on CoddyKit. This is lesson 3 of 4. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the R Academy learning path, and your progress syncs across the web and the CoddyKit app. The R Academy course includes 4 lessons in total.
Why Vectorization Matters
R is an interpreted language, so for loops have overhead on every iteration — function call dispatch, bounds checking, type coercion. Vectorized operations push that work into compiled C code that runs orders of magnitude faster.
Vectorization is the single most impactful optimization available in base R.
Loop vs cumsum() Example
Computing a running total with a for loop versus the built-in cumsum() shows the gap clearly. cumsum() calls C-level compiled code and processes the entire vector in one pass.
n <- 500000
x <- rnorm(n)
t_loop <- system.time({
result <- numeric(n)
result[1] <- x[1]
for (i in 2:n) result[i] <- result[i-1] + x[i]
})['elapsed']
t_vec <- system.time({
result2 <- cumsum(x)
})['elapsed']
cat('Loop :', t_loop, 's
')
cat('cumsum:', t_vec, 's
')ifelse() vs for + if
ifelse(condition, yes, no) is a vectorized conditional that evaluates the condition across an entire vector at once. It replaces element-by-element for + if loops with a single C-level pass.
n <- 300000
x <- rnorm(n)
t_loop <- system.time({
result <- numeric(n)
for (i in seq_len(n)) result[i] <- if (x[i] > 0) x[i] else -x[i]
})['elapsed']
t_vec <- system.time({
result2 <- ifelse(x > 0, x, -x)
})['elapsed']
cat('for+if :', t_loop, 's
')
cat('ifelse :', t_vec, 's
')Pre-allocating Result Vectors
When a loop is unavoidable, pre-allocate the result vector before the loop. Growing a vector with c(result, new_val) inside a loop copies the entire vector on each iteration — O(n^2) total memory operations.
n <- 20000
t_grow <- system.time({
result <- c()
for (i in seq_len(n)) result <- c(result, i^2)
})['elapsed']
t_prealloc <- system.time({
result2 <- numeric(n)
for (i in seq_len(n)) result2[i] <- i^2
})['elapsed']
cat('Growing vector:', t_grow, 's
')
cat('Pre-allocated :', t_prealloc, 's
')Correct Pre-allocation Types
Use the typed constructor matching your data to avoid implicit coercion during pre-allocation:
numeric(n)— double-precision floatsinteger(n)— integerscharacter(n)— empty stringslogical(n)— FALSE valuesvector('list', n)— list of NULLs
n <- 5
cat('numeric :', numeric(n), '
')
cat('integer :', integer(n), '
')
cat('logical :', logical(n), '
')
cat('character:', character(n), '
')
cat('list len :', length(vector('list', n)), '
')colSums() and rowSums() vs apply()
For matrix operations, colSums(m), rowSums(m), colMeans(m), and rowMeans(m) are heavily optimized C routines. They are consistently faster than apply(m, 1, sum) which dispatches the R function sum once per row.
m <- matrix(rnorm(1000 * 2000), nrow = 1000)
t_apply <- system.time(apply(m, 2, sum))['elapsed']
t_colsums <- system.time(colSums(m))['elapsed']
cat('apply(m,2,sum):', t_apply, 's
')
cat('colSums(m) :', t_colsums, 's
')Vectorized Arithmetic Is Always Fast
Basic arithmetic on vectors — +, -, *, /, ^, sqrt(), log(), exp() — are all vectorized. They operate element-wise across an entire vector in a single C call. Always prefer them over loops.
x <- 1:1000000
t1 <- system.time(y <- x^2 + 2*x + 1)['elapsed']
t2 <- system.time({
y2 <- numeric(length(x))
for (i in seq_along(x)) y2[i] <- x[i]^2 + 2*x[i] + 1
})['elapsed']
cat('Vectorized:', t1, 's
')
cat('Loop :', t2, 's
')Logical Subsetting Instead of Loops
Filtering a vector with a logical condition is vectorized. Instead of looping and conditionally appending, create a logical index and subset once — the underlying C code does one pass.
x <- rnorm(500000)
t_loop <- system.time({
pos <- c()
for (v in x) if (v > 0) pos <- c(pos, v)
})['elapsed']
t_vec <- system.time({
pos2 <- x[x > 0]
})['elapsed']
cat('Loop filter:', t_loop, 's
')
cat('Logical idx:', t_vec, 's
')which() and tabulate() for Index Work
When you need positions of TRUE values, which(condition) is vectorized and fast. tabulate(bin_vector) counts integer occurrences faster than table() for dense integer ranges.
x <- sample(1:10, 100000, replace = TRUE)
t_table <- system.time(table(x))['elapsed']
t_tabulate <- system.time(tabulate(x, nbins = 10))['elapsed']
cat('table() :', t_table, 's
')
cat('tabulate():', t_tabulate, 's
')
idx <- which(x == 5)
cat('Positions of 5: first 5 =', head(idx, 5), '
')When Loops Are Still Acceptable
Not every loop is bad. Loops are acceptable when:
- Each iteration depends on the previous result (sequential dependency)
- The number of iterations is small (< 1000)
- The body of the loop calls a complex function with no vectorized equivalent
In these cases, focus on pre-allocation and avoid growing structures inside the loop.
# Sequential dependency -- loop is correct here
fib <- function(n) {
result <- integer(n)
result[1] <- 1L
if (n >= 2) result[2] <- 1L
for (i in seq_len(n)[-c(1,2)]) result[i] <- result[i-1] + result[i-2]
result
}
cat('Fibonacci:', fib(10), '
')Vectorization Summary
Key vectorization rules for fast R code:
- Use
cumsum/cumprod/difffor sequential accumulation - Use
ifelse()for element-wise conditionals - Pre-allocate with
numeric(n)/vector('list',n) - Use
colSums/rowSums/colMeans/rowMeansoverapply() - Logical subsetting beats filtering loops
Quick Check: Pre-allocation
Why is growing a vector with result <- c(result, new_val) inside a loop so slow for large n?
Vectorization Recap
Vectorization is R's primary performance lever:
- Vectorized functions (
cumsum,ifelse, arithmetic operators) call compiled C code — they are 10x-100x faster than equivalent R loops - Pre-allocate result containers before any unavoidable loop to avoid O(n^2) copying
colSums/rowSumsbeatapply()for matrix aggregations- Logical subsetting replaces filtering loops cleanly and quickly
Learn R with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 43
- Lessons
- 159
Frequently Asked Questions
Is the “Vectorization for Speed” lesson free?
Yes — any 3 lessons of the R Academy learning path, including “Vectorization for Speed”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The R Academy course includes 4 lessons in total.
What will I learn in “Vectorization for Speed”?
Replace explicit loops with vectorized operations for major speedups. You practise R Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start R Academy?
No prior experience is required. R Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 3 of 4.
How long does the “Vectorization for Speed” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this R Academy lesson?
Yes. Every R Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- system.time() and proc.time()
- Profiling Code with Rprof and profvis
- Vectorization for Speed
- Benchmarking with microbenchmark