R Programming Quiz: Vectorization Performance
10 questions · exam conditions
0:00
Vectorization PerformanceQuestion 1 of 10

Two functions transform numeric vectors. One uses x * 2 + 1; the other uses a preallocated for loop. On vectors of length two, repeated benchmarks show only a small difference, although the vectorized function is much faster on long vectors.

Which explanation best fits this pattern?

For tiny inputs, fixed function and allocation costs are large relative to the few loop iterations that vectorization avoids.
R delays all vector arithmetic until a minimum vector length is reached, so both functions initially use the loop implementation.
The benchmark caches the two output values after the first call, but caching is disabled automatically for long vectors.
Vectorized arithmetic has quadratic setup cost, which becomes negligible only when enough elements are processed afterward.
← Back to quizzes

R Programming Quiz

R Programming Quiz: Vectorization Performance

Practice Vectorization Performance in R Programming with focused quiz questions that help you check what you know, review explanations, and build confidence with test-style prompts.

What this quiz covers

This quiz focuses on Vectorization Performance, giving you a quick way to practice the rules, question types, and explanations that matter most for R Programming.

How to use this quiz

Try each quiz question before looking at the correct answer. Use the explanations to review missed ideas, then come back to similar questions until the pattern feels familiar.

All questions

Question 1

Two functions transform numeric vectors. One uses x * 2 + 1; the other uses a preallocated for loop. On vectors of length two, repeated benchmarks show only a small difference, although the vectorized function is much faster on long vectors.

Which explanation best fits this pattern?

  1. For tiny inputs, fixed function and allocation costs are large relative to the few loop iterations that vectorization avoids. (correct answer)
  2. R delays all vector arithmetic until a minimum vector length is reached, so both functions initially use the loop implementation.
  3. The benchmark caches the two output values after the first call, but caching is disabled automatically for long vectors.
  4. Vectorized arithmetic has quadratic setup cost, which becomes negligible only when enough elements are processed afterward.
Explanation: When benchmarking functions in R, you need to think beyond just the core computation — every function call carries overhead: memory allocation, stack frames, argument checking, and interpreter bookkeeping. On tiny inputs, these fixed costs dominate the total runtime, which is exactly what this question tests. For a two-element vector, the vectorized version (x * 2 + 1) and the preallocated for loop are each paying nearly identical fixed costs — setting up the function environment, allocating the result vector, returning the value — while the actual computational work (two multiplications and two additions) is trivially small either way. The vectorized version's advantage lies in avoiding per-iteration R-level interpreter overhead, but when there are only two iterations, that savings is negligible compared to shared overhead. As vector length grows, the loop's per-element interpreter cost compounds while vectorization's fixed cost amortizes across thousands of elements, explaining why the gap widens dramatically on long vectors. A is correct because it precisely captures this fixed-cost-dominates-small-input dynamic. B is wrong because R has no such deferred evaluation threshold — vectorized operations run natively via compiled C code immediately, regardless of length. C describes a fictional caching mechanism; R does not automatically memoize function outputs, and no such cache-disabling behavior exists for long vectors. D is wrong because vectorized arithmetic has roughly constant (not quadratic) setup cost — the overhead is O(1) or at most O(n) in memory allocation, never quadratic. A useful rule of thumb: whenever a benchmark shows two approaches performing similarly at small scales but diverging at large scales, look for per-element versus fixed costs as the explanation.

Question 2

An explicit numeric loop is compiled with compiler::cmpfun(). After compilation it becomes noticeably faster and narrows, but does not eliminate, the gap with a vectorized base-R expression.

Which explanation best accounts for this result?

  1. Byte compilation changes the loop into a constant-time operation, while the vectorized expression must still scan the entire vector.
  2. Byte compilation automatically sends independent iterations to multiple cores, but vectorized base functions remain sequential.
  3. Byte compilation removes all allocations and bounds checks, making any remaining difference a benchmarking measurement error.
  4. Byte compilation reduces interpreter overhead, while vectorized primitives can still use tighter compiled loops over whole vectors. (correct answer)
Explanation: When thinking about R performance, it helps to understand the two distinct layers where speed is gained or lost: the interpreter overhead that governs how R executes each instruction, and the underlying compiled routines that do the actual numerical work. Byte compilation via compiler::cmpfun() targets the first layer. It translates your R loop into bytecode, reducing the per-iteration cost of parsing, dispatching, and interpreting R expressions. This is a real speedup — but it only tightens the loop's overhead; it doesn't change what the loop is doing structurally. Vectorized base-R functions like sum() or cumsum(), by contrast, drop immediately into tightly optimized C code that iterates over contiguous memory with minimal overhead and often benefits from CPU-level optimizations. That's why D is correct: byte compilation closes the gap by cutting interpreter tax, but vectorized primitives maintain an edge through their own compiled, whole-vector loops. A is wrong because byte compilation absolutely does not convert a loop into a constant-time operation — it still iterates O(n) times. The vectorized function also scans the entire vector, so this framing is backwards. B is wrong because byte compilation has nothing to do with parallelism or multi-core execution; it's purely a single-threaded optimization. C is wrong on two counts: byte compilation does not remove all allocations or bounds checks, and dismissing a consistent, reproducible performance gap as "measurement error" is not scientifically sound. A useful study rule: whenever a question contrasts loops with vectorized code, ask yourself where each option's speedup would actually come from — interpreter level, memory level, or hardware level. That distinction almost always points to the right answer.

Question 3

A programmer replaces for (i in seq_along(files)) result[[i]] <- analyze(files[[i]]) with lapply(files, analyze). The two versions have nearly identical running times because analyze() is an R function that performs substantial work for each file.

Which explanation best accounts for the limited speed improvement?

  1. lapply() first combines all files into one vector, and the cost of that combination cancels its vectorization advantage.
  2. lapply() compiles analyze() separately for every file, making its setup cost comparable to an explicit loop.
  3. lapply() uses the same source-level for statement, so there can never be any performance difference between them.
  4. lapply() manages iteration internally but still invokes the R callback per element, whose cost can dominate execution. (correct answer)
Explanation: When comparing lapply() to an explicit for loop in R, the key question to ask is: where does the actual time go? If the work inside each iteration is expensive, the mechanism controlling iteration matters far less than the work itself. This is exactly what option D captures. lapply() does manage iteration in C internally, which avoids some of R's interpreter overhead — but it still must call your R function analyze() once per element. When analyze() performs substantial computation, that per-call cost completely dominates the total runtime. The small savings from C-level looping become negligible compared to the heavy lifting inside the callback. This is why you observe nearly identical running times: the bottleneck is the work, not the loop mechanism. Option A is wrong because lapply() does not concatenate inputs into a single vector before processing. It iterates over elements individually, so no such combination cost exists. Option B introduces a fictional concept — R does not recompile functions on each lapply() call. analyze() is parsed and compiled once; repeated invocation doesn't trigger repeated compilation. Option C is technically false in the opposite direction: lapply() is not implemented as a source-level for loop. It uses a C-level loop internally, which is why it can be faster for lightweight operations — but that advantage simply doesn't show up when the callback dominates. The study tip here: whenever a question asks why lapply() doesn't speed things up, ask yourself what fraction of time the callback function consumes versus the iteration mechanism. If the callback is slow, no amount of loop optimization will matter much.

Question 4

For one input size, an explicit R loop takes about 80 milliseconds and a vectorized version takes about 10 milliseconds. When the input size is doubled, the loop takes about 160 milliseconds and the vectorized version takes about 20 milliseconds.

Which conclusion is most consistent with these measurements?

  1. Both methods scale roughly linearly, but the vectorized version has a substantially smaller per-element overhead. (correct answer)
  2. The vectorized method is effectively constant time because its source code contains only one expression.
  3. The explicit loop has quadratic growth because R must interpret a for statement on each iteration.
  4. The vectorized method must be skipping most elements, because equivalent algorithms cannot differ by that factor.
Explanation: When analyzing algorithm performance, focus on how runtime scales with input size — this is the heart of complexity analysis. The key observation here is simple: when input doubles, what happens to the time? In both cases, doubling the input doubles the runtime — 80→160 ms for the loop, 10→20 ms for the vectorized version. That's the hallmark of linear scaling, or O(n) growth. However, the vectorized version consistently runs 8× faster, meaning it has a much smaller constant overhead per element. This is precisely what answer A describes, making it the correct conclusion. Answer B is wrong because "constant time" means runtime doesn't change as input grows — but the vectorized version clearly does grow (10→20 ms). The number of expressions in source code has nothing to do with algorithmic complexity; that's a fundamental misconception about how R works internally. Answer C is wrong because the loop also shows linear (not quadratic) growth. Quadratic growth would mean doubling the input quadruples the runtime. The for statement is interpreted once per iteration, but that contributes a constant per-element cost — it doesn't cause quadratic behavior on its own. Answer D is wrong because two algorithms solving the same problem can absolutely differ by a constant factor. Vectorized functions like sum() or * are implemented in compiled C code, which operates far more efficiently than R's interpreted loop overhead. The factor difference reflects implementation, not correctness. Your study tip: always check how runtime grows, not just how much faster one approach is. Linear means doubling input doubles time — memorize that pattern and you'll cut through most complexity questions quickly.

Question 5

A programmer sums a large double vector with total <- total + x[i] inside a preallocated, otherwise simple R loop. Replacing the loop with sum(x) produces the same result for the tested data and is much faster.

Why can sum(x) have a large performance advantage even though it must still inspect every element?

  1. It converts the vector to a scalar before reading it, so only the first stored value must be accessed.
  2. It performs the reduction in compiled code, avoiding R-level addition, indexing, and assignment on each iteration. (correct answer)
  3. It has a lower algorithmic growth rate because reductions do not need to process elements after the running total stabilizes.
  4. It stores a separate partial sum for every element, allowing base R to parallelize the reduction automatically.
Explanation: When you see performance questions comparing R loops to built-in functions, the key concept is interpreted vs. compiled execution. R loops run at the interpreter level, meaning every iteration pays overhead for R-level parsing, variable lookup, indexing, and assignment. Built-in functions like sum() sidestep this entirely. Here's why B is correct: sum(x) is implemented in compiled C code inside R's internals. When you call it, R hands the entire vector to a tight C loop that performs addition with no R interpreter involvement per element. Your manual loop, by contrast, executes x[i] (an R indexing call), total + x[i] (an R addition), and total <- ... (an R assignment) on every single iteration — each carrying interpreter overhead. Both approaches visit every element, but sum() does so with a fraction of the per-element cost. A is wrong because sum() does not convert the vector to a scalar first or skip elements — it reads all of them. This describes a nonexistent shortcut. C is wrong because there is no algorithmic shortcut here. Both the loop and sum() are O(n)O(n); the advantage is constant-factor overhead reduction, not a lower growth rate. A "stabilized running total" that stops processing simply doesn't exist for general floating-point sums. D is wrong because base R's sum() does not automatically parallelize — it runs sequentially in C. Parallelism would require explicit packages like parallel or foreach. Study tip: On R performance questions, always ask where the code executes — R interpreter vs. compiled C. That distinction explains most speed differences between loops and vectorized or built-in functions.

Question 6

A programmer compares two functions for a large numeric vector x. The first preallocates out and executes out[i] <- sqrt(x[i]) + 1 inside a for loop. The second returns sqrt(x) + 1. Both functions produce the same values.

Which explanation best accounts for the second function usually running faster?

  1. It performs fewer square-root calculations because sqrt() evaluates only selected elements when passed a vector.
  2. It moves the elementwise work into compiled routines, avoiding repeated interpretation and dispatch of the loop body. (correct answer)
  3. It automatically divides the vector among processor cores, whereas an explicit for loop always uses one core.
  4. It changes the task from linear growth to constant-time growth by evaluating the vector as one R expression.
Explanation: When comparing R loops to vectorized operations, the key question to ask is: where does the computation actually happen? R is an interpreted language, meaning each line of code must be parsed and dispatched at runtime. This overhead is manageable for a single expression, but inside a for loop it compounds with every iteration. This is exactly why B is correct. When you call sqrt(x) + 1 on a vector, R hands the entire computation off to internal C routines. Those routines iterate over the elements in compiled machine code — no repeated parsing, no per-iteration function dispatch, no interpreter overhead. The loop still happens; it just happens at the C level, which is dramatically faster than R's interpreter repeating that work thousands of times. A is wrong because sqrt() does not skip elements when passed a vector — it computes the square root of every element, just as the loop does. There is no selective evaluation shortcut. C is a tempting distractor because parallelism sounds like a reasonable performance explanation, but base R's vectorized functions are not automatically parallelized. They run on a single core by default, just like a for loop. Parallelism requires explicit packages like parallel or future. D mischaracterizes the complexity. Processing n elements still scales linearly whether you use a loop or a vectorized call — the operation is O(n) either way. The speedup comes from constant factors (compiled vs. interpreted overhead), not from a change in asymptotic growth. Study tip: Whenever a question asks why vectorization is faster in R, the answer almost always points to compiled internals and reduced interpreter overhead — not parallelism or skipped computations.

Question 7

A loop calculates y[i] <- 0.7 * y[i - 1] + x[i], so each result depends on the preceding result. A programmer attempts to accelerate it with one elementwise expression using shifted portions of y and x.

What is the most accurate assessment of this attempted vectorization?

  1. It is equivalent because R applies the left-side assignment element by element, interleaving it with evaluation of the right-side expression.
  2. It is generally not equivalent because each output value depends on the previous one; a native routine designed for recurrences may help while preserving correctness. (correct answer)
  3. It is equivalent only when y is preallocated, because preallocation causes R to perform vector assignments strictly from left to right.
  4. It is necessarily slower because recurrences that reference previous values always have quadratic growth in any compiled implementation.
Explanation: Whenever you see a question about vectorization in R, ask yourself: does each computed value depend on a previously computed value in the same operation? That dependency is the key to evaluating whether a loop can be replaced with a vectorized expression. Here, the recurrence y[i]=0.7y[i1]+x[i]y[i] = 0.7 \cdot y[i-1] + x[i] means that to compute y[3], you need the already updated y[2], which itself required the updated y[1]. A single elementwise expression like y[-1] <- 0.7 * y[-length(y)] + x[-1] evaluates the entire right-hand side using the original values of y before any assignment occurs. This breaks the chain of dependencies, producing incorrect results for all but the first step. Answer B is correct: the attempted vectorization is generally not equivalent, and for true recurrences, you'd want a compiled routine (like those in the dsp or signal packages, or a custom Rcpp solution) that processes values sequentially while avoiding R's interpreter overhead. Answer A is wrong because R does not interleave assignment with evaluation element by element — the entire right-hand side is fully evaluated first, then assigned. Answer C is wrong because preallocation affects memory efficiency, not evaluation order; it does not cause R to assign values left-to-right during a vectorized expression. Answer D is wrong because recurrences are not inherently quadratic — compiled sequential implementations run in linear time; the problem is correctness, not computational complexity. Study tip: When evaluating vectorization, always check for sequential dependencies. If output i feeds into output i+1, a single vectorized expression cannot safely replace the loop.

Question 8

An initial loop builds a result using out <- c(out, f(x[i])) on every iteration. It is much slower than a vectorized expression. After the programmer preallocates out <- numeric(length(x)) and assigns by index, the loop becomes much faster but remains slower than the vectorized version.

Which interpretation best explains both changes in performance?

  1. Preallocation converts the loop into a vectorized operation, but indexed assignment prevents R from using all processor cores.
  2. Preallocation matters only because numeric() skips missing values, while vectorization avoids checking for them entirely.
  3. Growing the result and using a vector expression have different growth rates, so preallocation can only make a minor difference.
  4. Preallocation avoids repeated copying and allocation, while the remaining gap can reflect per-iteration R interpreter overhead. (correct answer)
Explanation: When reasoning about R performance, think in terms of what operations the computer must perform on each iteration — memory allocation, copying, and interpretation all have costs that compound differently. The key insight here is that growing a vector with out <- c(out, f(x[i])) forces R to allocate a brand-new vector on every single iteration, copy all existing elements into it, then append the new one. For a vector of length n, this produces roughly O(n2)O(n^2) total copy operations. Preallocating with out <- numeric(length(x)) eliminates that cascading copy problem — now each iteration only writes one value into an already-existing slot, bringing memory overhead down to O(n)O(n). That explains the first speedup. The remaining gap versus a fully vectorized expression exists because the loop still visits the R interpreter on every iteration — function call dispatch, type checking, and bookkeeping overhead accumulate n times. A vectorized operation pushes all of that work into a single compiled C-level routine, bypassing per-iteration interpreter costs entirely. Answer D captures both mechanisms cleanly and correctly. Answer A is wrong because preallocation does nothing to enable parallelism or multi-core execution — it simply fixes the memory growth problem; those are entirely separate concerns. Answer B invents a fictional explanation: numeric() initializes zeros and has no special logic for missing values, and that has nothing to do with the performance difference. Answer C is wrong in the opposite direction — it understates the impact of preallocation, which actually changes the algorithmic complexity class, not just a minor constant factor. As a study tip, remember that R performance questions often layer two distinct costs: memory allocation strategy and interpreter overhead. Recognize that preallocation solves the first but not the second.

Question 9

For a very large vector, a programmer writes exp(x) / (1 + exp(x)). A carefully written preallocated loop that computes exp(x[i]) once per element unexpectedly runs faster and uses much less memory.

What is the best explanation for this result?

  1. Explicit loops use a more accurate exponential algorithm, so they can terminate each calculation earlier than exp(x).
  2. Vectorized operations become quadratic for large vectors because each native routine rescans all previously processed elements.
  3. The vectorized expression repeats an expensive calculation and creates temporaries, which can outweigh its lower dispatch overhead. (correct answer)
  4. The loop is automatically parallelized because its iterations are independent, while compound vector expressions remain single-threaded.
Explanation: When evaluating R code performance, you should think about two distinct costs: memory allocation (creating intermediate objects) and redundant computation (repeating expensive work). Both can silently dominate runtime for large vectors. Consider what exp(x) / (1 + exp(x)) actually does in R. First, it computes exp(x), allocating a full vector of length n. Then it computes exp(x) again as a separate subexpression — another full n-length vector. Finally, 1 + exp(x) creates yet another temporary. For a large vector, you've allocated roughly 3× the memory of x and called the expensive exp function twice per element. A preallocated loop that stores exp(x[i]) in a local variable computes it exactly once per element and never creates large intermediates, so C is correct: the vectorized form repeats an expensive calculation and generates temporaries that can outweigh its lower function-dispatch overhead. A is wrong because exp() uses the same underlying math library routines whether called on a scalar or a vector — there's no alternate "scalar" algorithm that exits early. B is wrong because vectorized operations in R are linear, not quadratic; they process each element once without rescanning prior elements. D is wrong because R does not automatically parallelize for-loops — parallelism requires explicit packages like parallel or foreach. The loop's speed advantage here comes purely from reduced allocation and computation, not threading. As a study tip: whenever you see a vectorized expression that reuses a subexpression, ask yourself how many times that computation runs and how many temporary vectors get allocated. Rewriting with a local variable or using plogis(x) (R's built-in logistic function) is often the real fix.

Question 10

A profiler reports that log(x) + x^2 uses only one processor core, yet it is substantially faster than an explicit R loop computing the same values.

Which statement best reconciles these observations?

  1. The profiler must be incorrect, because vectorized expressions obtain their speed primarily by using multiple cores.
  2. The vectorized expression examines fewer elements by inferring repeated values, even when the input has no such pattern.
  3. Vectorization can be faster through compiled sequential loops and efficient memory access without requiring parallel execution. (correct answer)
  4. The explicit loop has a worse growth rate solely because one-core execution makes every additional iteration more expensive.
Explanation: When you see a question about vectorization speed in R, resist the instinct to assume parallelism is the explanation. The real question is: why is compiled, sequential code faster than an interpreted loop? R's vectorized functions like log() and arithmetic operators are implemented in pre-compiled C or Fortran code. Even running on a single core, this compiled code executes a tight, cache-friendly loop over contiguous memory — avoiding the per-iteration overhead that R's interpreter incurs for every step of an explicit for loop. That overhead includes parsing, type-checking, and dispatching operations repeatedly. Vectorization eliminates that per-element tax, which is why C answers this correctly: vectorization achieves its speed through compiled sequential execution and efficient memory access, not parallel processing. A is wrong because it assumes vectorization's speed requires multiple cores — this is a common misconception. Parallelism can add speed, but it is neither the primary mechanism nor a requirement for vectorization to outperform loops. The profiler showing single-core usage is entirely consistent with a correct result. B describes something that doesn't happen. R's vectorized functions don't skip elements or infer repeated values — they process every element. This confuses vectorization with something like run-length encoding or lazy evaluation. D is subtly wrong because it attributes the explicit loop's slowness to single-core execution specifically, implying the loop would be competitive if parallelized. The real bottleneck is interpreter overhead, not core count. As a study tip: whenever R performance questions mention "single core," don't assume that rules out speed gains — interpreted overhead, not parallelism, is usually the culprit.