R Academy · Lesson

Vectorization for Speed

Replace explicit loops with vectorized operations for major speedups.

Lesson 3 of 413 steps

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 floats
  • integer(n) — integers
  • character(n) — empty strings
  • logical(n) — FALSE values
  • vector('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/diff for sequential accumulation
  • Use ifelse() for element-wise conditionals
  • Pre-allocate with numeric(n) / vector('list',n)
  • Use colSums/rowSums/colMeans/rowMeans over apply()
  • 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/rowSums beat apply() for matrix aggregations
  • Logical subsetting replaces filtering loops cleanly and quickly
Free to start

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

  1. system.time() and proc.time()
  2. Profiling Code with Rprof and profvis
  3. Vectorization for Speed
  4. Benchmarking with microbenchmark
← Back to R Academy