Vectorization vs Loops

Reviewed & published by Brayan K

Vectorization is the practice of applying an operation to an entire vector at once instead of looping element by element, producing shorter, faster, and more idiomatic R code.

Part of the free R course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

You'll see why vectorized operations beat for-loops in R, how recycling lets vectors of different lengths combine, how ifelse(), pmax/pmin, and rowSums/colMeans replace loops, and when a plain loop is still the right call.

What You'll Learn in This Lesson

1️⃣ The Same Job: Loop vs Vectorized

Here is the exact same task — add 20% to every price — written both ways. The loop is correct but verbose and, on large data, noticeably slower. The vectorized version is one line and runs the repetition in fast compiled code.

# THE LOOP WAY (works, but verbose and slow in R):
prices <- c(10, 20, 30, 40)
out <- numeric(length(prices))
for (i in seq_along(prices)) {
  out[i] <- prices[i] * 1.2
}
out                 # -> [1] 12 24 36 48

# THE VECTORIZED WAY (idiomatic R — one line, faster):
prices * 1.2        # -> [1] 12 24 36 48

Both print the same result. The difference is clarity and speed: the vectorized form is what experienced R users write by default.

2️⃣ Recycling: How Lengths Combine

Vectorization relies on recycling: when two vectors differ in length, R repeats the shorter to match the longer. A plain number is just a length-1 vector, which is why x + 1 adds 1 to everything. If the longer length isn't a clean multiple of the shorter, R still computes but warns you.

# Recycling: when lengths differ, R repeats the shorter vector.
c(1, 2, 3, 4) + c(10, 20)   # 1+10, 2+20, 3+10, 4+20 -> [1] 11 22 13 24

# A single number is just a length-1 vector, recycled to fit:
c(5, 10, 15) + 1            # -> [1]  6 11 16

# If the longer length isn't a multiple of the shorter, R still
# recycles but WARNS you (the result below is correct, plus a warning):
c(1, 2, 3) + c(10, 20)      # -> [1] 11 22 13  (with a warning)

3️⃣ Vectorized Branching & Reductions

ifelse() applies a condition across a whole vector and returns a same-length result — the vectorized replacement for an if/else loop. pmax() and pmin() do parallel (element-wise) max/min across two vectors.

# ifelse() is the vectorized cousin of if/else — it works on a
# whole vector at once and returns a vector the same length.
temps <- c(15, 28, 9, 33, 21)
ifelse(temps > 20, "warm", "cool")
# -> [1] "cool" "warm" "cool" "warm" "warm"

# pmax / pmin compare vectors element-by-element (parallel max/min):
pmax(c(1, 8, 3), c(5, 2, 9))   # -> [1] 5 8 9
pmin(c(1, 8, 3), c(5, 2, 9))   # -> [1] 1 2 3

For tabular data, rowSums(), colSums(), rowMeans(), and colMeans() reduce a whole matrix in one vectorized call — vastly faster than nested loops.

# Whole-matrix reductions are vectorized too — far faster than loops.
m <- matrix(c(1, 2, 3, 4, 5, 6), nrow = 2, byrow = TRUE)
m
#      [,1] [,2] [,3]
# [1,]    1    2    3
# [2,]    4    5    6

rowSums(m)     # sum across each row    -> [1]  6 15
colMeans(m)    # mean down each column  -> [1] 2.5 3.5 4.5

Your turn. Fill in the ___ blanks, run it, and compare with the expected output.

# 🎯 YOUR TURN — replace each ___ then run it.
celsius <- c(0, 20, 37, 100)

# 1) Convert ALL to Fahrenheit in ONE vectorized line: F = C * 9/5 + 32
fahr <- celsius ___ 9/5 + 32     # 👉 replace ___ with *
print(fahr)

# 2) Label each as "hot" if >= 100F else "ok" with ifelse()
print(ifelse(fahr >= 100, "hot", "ok"))

# ✅ Expected output:
# [1]  32.0  68.0  98.6 212.0
# [1] "ok"  "ok"  "ok"  "hot"

Mini-Challenge: Grade a Class

The constraint is the point: solve every step with vectorized functions and no for-loop. If you find yourself reaching for a loop, ask which built-in does the same job in one line.

# 🎯 MINI-CHALLENGE: grade a class without a loop.
#
# scores <- c(55, 82, 91, 47, 68, 100)
#
# 1. Use ifelse() to label each "pass" (>= 60) or "fail".
# 2. Count how many passed with sum(scores >= 60).
# 3. Cap every score at 90 using pmin(scores, 90).
# 4. Print the class average with mean(scores).
#
# Do it all WITHOUT writing a for-loop.
#
# ✅ Example output:
#    [1] "fail" "pass" "pass" "fail" "pass" "pass"
#    [1] 4
#    [1] 55 82 90 47 68 90
#    [1] 73.83333

# your code here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Vectorization

TaskR Syntax
Scale every elementx * 1.2
Element-wise branchifelse(x > 20, "a", "b")
Parallel max/minpmax(a, b); pmin(a, b)
Row sums / col meansrowSums(m); colMeans(m)
Loop index safelyfor (i in seq_along(x))
Pre-allocate outputout <- numeric(length(x))

🎉 Lesson Complete!

Practice quiz

What does the vectorized expression prices * 1.2 do?

  • Multiplies every element by 1.2 at once
  • Multiplies only the first element
  • Returns a single number
  • Errors on vectors

Answer: Multiplies every element by 1.2 at once. Vectorized arithmetic applies the operation to every element.

Why are vectorized operations usually faster than R for-loops?

  • They skip elements
  • The repetition runs in compiled C, not the R interpreter
  • They use less memory only
  • They run in parallel threads

Answer: The repetition runs in compiled C, not the R interpreter. Vectorized functions push the loop down into optimised C code.

What is recycling in R?

  • Garbage collection
  • Reusing variable names
  • Repeating the shorter vector to match the longer one
  • Caching results

Answer: Repeating the shorter vector to match the longer one. When lengths differ, R repeats the shorter vector element-wise.

What does c(1, 2, 3) + c(10, 20) produce?

  • An error that stops execution
  • c(11, 22)
  • c(11, 22, 33)
  • c(11, 22, 13) with a warning

Answer: c(11, 22, 13) with a warning. R recycles (1+10, 2+20, 3+10) and warns because 3 is not a multiple of 2.

What does ifelse(temps > 20, "warm", "cool") return?

  • A same-length vector of "warm"/"cool"
  • A single string
  • Only the first result
  • TRUE or FALSE

Answer: A same-length vector of "warm"/"cool". ifelse() is vectorized and returns a vector the same length as the test.

How does a plain if () differ from ifelse()?

  • if() is vectorized
  • if() takes one TRUE/FALSE and uses only the first element of a vector
  • They are identical
  • ifelse() only works on numbers

Answer: if() takes one TRUE/FALSE and uses only the first element of a vector. if() is for control flow; ifelse() transforms a whole vector.

What does pmax(c(1, 8, 3), c(5, 2, 9)) return?

  • 9 (the overall max)
  • c(1, 2, 3)
  • c(5, 8, 9)
  • A single value

Answer: c(5, 8, 9). pmax() takes the parallel (element-wise) maximum across vectors.

How is pmax() different from max()?

  • pmax() sorts
  • They are the same
  • max() is vectorized; pmax() is not
  • max() returns one number; pmax() returns an element-wise vector

Answer: max() returns one number; pmax() returns an element-wise vector. max() reduces to one value; pmax() compares element by element.

What does rowSums(m) compute for a matrix m?

  • The grand total
  • The sum across each row
  • The sum down each column
  • The matrix mean

Answer: The sum across each row. rowSums() sums across each row in one vectorized call.

When you must keep a for-loop, what avoids the classic R slowdown?

  • Use 1:length(x)
  • Grow the result with c() each iteration
  • Pre-allocate the output (e.g. out <- numeric(n))
  • Disable warnings

Answer: Pre-allocate the output (e.g. out <- numeric(n)). Pre-allocating avoids repeatedly copying a growing vector.

Continue this course

Frequently asked questions

Why are vectorized operations faster than for-loops in R?

When you write prices * 1.2, the loop over each element still happens — but it runs inside R's compiled C code, not in the interpreted R layer. A for-loop in R re-enters the interpreter on every iteration, paying overhead each time, which adds up fast for large vectors. Vectorized functions push the whole repetition down into optimised C, so they are typically many times faster and also shorter and easier to read. The rule: if a built-in vectorized function exists, prefer it.

What is recycling and when does it bite me?

Recycling is R repeating the shorter of two vectors to match the longer one during an element-wise operation. It is the reason prices + 5 works: the length-1 vector 5 is recycled across every element. It becomes a problem when the longer length is not an exact multiple of the shorter — R still recycles but issues a 'longer object length is not a multiple of shorter object length' warning, and the result is usually not what you intended. Treat that warning as a sign to check your vector lengths.

What is the difference between ifelse() and a regular if statement?

A regular if () takes a single TRUE/FALSE and chooses one branch — it is for control flow and only looks at the first element if you pass it a vector. ifelse() is vectorized: it takes a logical vector and returns a new vector the same length, picking the 'yes' value where the test is TRUE and the 'no' value where it is FALSE, element by element. Use if for program logic ('should I run this block?') and ifelse() for transforming data ('label each row').

Are for-loops ever the right choice in R?

Yes. A loop is fine — and sometimes clearer — when each iteration genuinely depends on the previous one (like a running simulation), when you are calling a side-effecting function (printing, writing files) rather than computing a value, or when the work per iteration is large enough that loop overhead is negligible. The key is to pre-allocate the output vector (out <- numeric(n)) rather than growing it with c() inside the loop, which is what makes naive R loops slow. Otherwise, reach for a vectorized function or the apply family.