R Programming Quiz: Vectorization Vs Loops
10 questions · exam conditions
0:00
Vectorization Vs LoopsQuestion 1 of 10

A loop constructs running sales totals by assigning the first sale to the first result element and then adding each later sale to the previously computed total.

Which implementation is generally preferred if the complete vector of running totals is required?

Use sum(sales) because a single vectorized reduction automatically returns every intermediate running total.
Use sales + c(0, sales[-length(sales)]) because adding the previous sale produces the cumulative totals.
Use cumsum(sales) because it is a specialized vectorized operation implementing the required cumulative dependency.
Retain the explicit loop because any computation depending on a previous result cannot use a vectorized function.
← Back to quizzes

R Programming Quiz

R Programming Quiz: Vectorization Vs Loops

Practice Vectorization Vs Loops 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 Vs Loops, 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

A loop constructs running sales totals by assigning the first sale to the first result element and then adding each later sale to the previously computed total.

Which implementation is generally preferred if the complete vector of running totals is required?

  1. Use sum(sales) because a single vectorized reduction automatically returns every intermediate running total.
  2. Use sales + c(0, sales[-length(sales)]) because adding the previous sale produces the cumulative totals.
  3. Use cumsum(sales) because it is a specialized vectorized operation implementing the required cumulative dependency. (correct answer)
  4. Retain the explicit loop because any computation depending on a previous result cannot use a vectorized function.
Explanation: When you see a question about cumulative computations in R, ask yourself two things: does a built-in vectorized function exist for this exact operation, and does it correctly handle sequential dependencies? That framing points directly to the right answer here. cumsum() is R's purpose-built function for computing running totals. Given sales = c(10, 20, 30), it returns c(10, 30, 60) — exactly the vector of intermediate totals the passage describes. It is vectorized, meaning R executes it in optimized compiled code rather than interpreted R loops, making it both faster and cleaner than a manual loop. This makes C the correct and preferred choice. A is wrong because sum(sales) performs a reduction — it collapses the entire vector into a single scalar (the grand total), discarding every intermediate value. It returns nothing resembling a running total vector. B contains a plausible-looking formula, but it is mathematically incorrect. Adding each sale to the immediately preceding sale (not the accumulated total) does not produce cumulative sums. For c(10, 20, 30), it yields c(10, 30, 50) instead of c(10, 30, 60) — off as soon as more than two elements exist. D represents a common misconception: that cumulative dependency automatically requires a loop. R's cumsum() proves this false — specialized vectorized functions can encode sequential logic internally. As a study tip, remember that R has a family of cumulative functions — cumsum(), cumprod(), cummax(), cummin() — and exam questions will test whether you recognize them as superior alternatives to explicit loops.

Question 2

A script must submit files to a service one at a time. After each submission, it records the returned identifier, waits if the service requests a delay, and may retry that same file before continuing. The service offers no bulk-submission endpoint.

Which approach best fits this task?

  1. Wrap the submission function with Vectorize() because this converts ordered side effects into one bulk service request.
  2. Use vapply() because it guarantees that all submissions occur simultaneously while preserving their input order.
  3. Submit the complete filename vector once because ordinary R functions always distribute scalar service calls elementwise.
  4. Use an explicit loop because retries, delays, and later actions depend on the outcome of each individual submission. (correct answer)
Explanation: When a task requires tracking the outcome of each step before deciding what to do next — retries, delays, conditional logic — you're dealing with sequential, stateful control flow. That's the key signal in this question. An explicit loop (D) is the right tool here because each iteration can inspect the server's response, decide whether to retry, pause if a delay is requested, and only then move to the next file. The loop body has full access to mutable state (the collected identifiers, retry counters, etc.) and executes exactly one submission at a time, which matches the service's constraint perfectly. Choice A misrepresents what Vectorize() actually does. It doesn't batch calls into a single request — it's a wrapper that applies a scalar function element-by-element, essentially building a loop for you. It also gives you no mechanism to inspect intermediate results, handle retries, or introduce conditional delays between calls. Choice B contains two false claims about vapply(). First, vapply() does not run submissions simultaneously — R is single-threaded by default and vapply() processes elements sequentially. Second, even if parallelism were involved, that would be exactly wrong for a service requiring one-at-a-time submissions with response-dependent behavior. Choice C is simply false. Passing a vector to a function does not automatically "distribute" it into multiple service calls; the function receives the entire vector as one argument unless it's explicitly written to loop internally. As a study tip: whenever a question describes side effects that depend on prior outcomes (retries, waits, conditional branching), an explicit loop is almost always the answer over functional-style apply tools.

Question 3

A vector value contains observations, and an integer vector group of the same length identifies each observation's group. Every entry of group is a valid position in the shorter vector threshold. A loop currently assigns flag[i] <- value[i] > threshold[group[i]].

Which expression correctly replaces the loop without relying on unintended recycling?

  1. Use value > threshold because recycling the threshold vector follows the group labels automatically.
  2. Use threshold > value[group] because indexing the observations applies each group's threshold in order.
  3. Use value[group] > threshold because group indexing aligns the observations with the shorter threshold vector.
  4. Use value > threshold[group] because indexing expands the appropriate threshold to each observation's position. (correct answer)
Explanation: When vectorizing a loop in R, your goal is to replicate exactly what the loop does — element by element — using index alignment rather than recycling. Here, the loop says: for each position i, compare value[i] to threshold[group[i]]. Notice that group[i] is used to index into threshold, not into value. So the vectorized form needs to expand threshold according to the group vector. D is correct because threshold[group] uses the group vector as an index, pulling the appropriate threshold for each observation in one shot. The result is a vector the same length as value and group, with each element being the threshold corresponding to that observation's group — exactly mirroring threshold[group[i]] across all i. A is wrong because value > threshold relies on R's recycling rules, which repeat threshold in order (1, 2, 3, 1, 2, 3, ...) regardless of what group contains. This only works correctly if groups happen to be perfectly sequential, which is not guaranteed. B reverses the comparison entirely — threshold > value[group] both flips the inequality and indexes value with group, which doesn't match the loop's logic on either count. C makes a symmetric mistake: it indexes value with group (value[group]) instead of indexing threshold with group. This reorders the observations rather than selecting the correct thresholds, producing a misaligned comparison. A useful pattern to remember: when a loop uses shortVector[indexVector[i]], the vectorized equivalent is shortVector[indexVector] — index the shorter structure, not the longer one.

Question 4

For each period, an account balance must be updated by applying that period's interest rate to the previously computed balance and then subtracting that period's withdrawal. Both rates and withdrawals vary by period, and every intermediate balance is needed.

Which implementation strategy is most appropriate in base R when no specialized financial function is being used?

  1. Preallocate the balance vector and use a loop because each new balance depends on the preceding computed balance. (correct answer)
  2. Compute initial_balance * cumprod(1 + rate) - cumsum(withdrawal) because compounding and withdrawals can be accumulated independently and then combined.
  3. Compute initial_balance + cumsum(initial_balance * rate - withdrawal) because each period's interest is determined by the fixed starting balance.
  4. Compute initial_balance * (1 + rate) - withdrawal because elementwise arithmetic automatically carries each period's ending balance into the next period.
Explanation: When a computation is path-dependent — meaning each step requires the result of the previous step — you cannot collapse it into a single vectorized expression. That's the core concept being tested here. In this problem, each period's balance follows: Bt=Bt1×(1+rt)wtB_t = B_{t-1} \times (1 + r_t) - w_t Because BtB_t depends on Bt1B_{t-1}, which itself was computed from Bt2B_{t-2}, the values form a chain. A loop that preallocates the balance vector and iterates forward — updating each element before moving to the next — correctly captures this dependency. That's why A is right: preallocating avoids repeated memory reallocation, and the loop enforces the sequential dependency. B is tempting because cumprod and cumsum sound like they handle compounding and withdrawals. But the formula only works when there are no withdrawals reducing the base before the next period's interest is applied. Once withdrawals occur mid-sequence, the two components cannot be accumulated independently — the withdrawal in period 1 changes the base that gets compounded in period 2, breaking the separation. C incorrectly assumes interest is always earned on the initial balance. In reality, interest compounds on the running balance, which shrinks after each withdrawal. D mistakes elementwise vector arithmetic for sequential updating. R applies initial_balance * (1 + rate) - withdrawal to the original scalar across all periods simultaneously — it doesn't "carry forward" any computed balance. Study tip: Whenever you see a recurrence relation where step tt explicitly depends on step t1t-1's output, that's your signal that a loop is necessary — no vectorized shortcut preserves that dependency.

Question 5

A programmer needs log(x) for positive, nonmissing elements of x and NA_real_ everywhere else. Using ifelse(x > 0, log(x), NA_real_) produces the desired final positions but also generates warnings because log(x) is evaluated for invalid elements.

Which replacement best retains vectorized processing while avoiding evaluation of log() at invalid positions?

  1. Use suppressWarnings(ifelse(x > 0, log(x), NA_real_)) so invalid logarithms are still computed but hidden.
  2. Preallocate an NA_real_ result, form a valid-position logical index, and assign log(x[valid]) only at those positions. (correct answer)
  3. Use log(x[x > 0]) because dropping invalid elements preserves both the original length and positional alignment.
  4. Loop over every element and call log() conditionally because logical indexing is not a vectorized operation.
Explanation: When working with vectorized operations in R, the key tension is between where a function is called and where its result is used. ifelse() looks conditional, but it fully evaluates both the true and false expressions across the entire vector before selecting results — meaning log(x) runs on negative and zero elements regardless, triggering warnings. The cleanest solution is B: preallocate a vector of NA_real_ with the same length as x, compute a logical index valid <- x > 0, then assign result[valid] <- log(x[valid]). This is genuinely vectorized — log() receives a contiguous numeric vector of only valid values in a single call — and it never touches invalid positions. Positional alignment is preserved because you're assigning back into the original index slots. A is a trap for students who confuse "hiding a problem" with "solving it." suppressWarnings() just silences the output; log() still runs on invalid elements, producing NaN values that could corrupt downstream analysis without any signal. C looks tempting because log(x[x > 0]) does avoid invalid inputs, but it returns a shorter vector stripped of invalid positions. You lose length and positional alignment entirely — a major problem if you need results to correspond to the original indices of x. D is simply false. Logical indexing like x[x > 0] is one of R's core vectorized tools, implemented in C under the hood. Replacing it with a loop sacrifices R's performance advantages for no benefit. As a study tip: whenever you see ifelse() with a function that has a restricted domain, remember that both branches evaluate eagerly — reach for index-and-assign instead.

Question 6

A large numeric matrix m contains some missing values, but every row has at least one observed value. The required output is one mean per row, excluding missing values.

Which implementation is generally preferred for clarity and performance?

  1. Use mean(m, na.rm = TRUE) because mean() automatically preserves the matrix's row structure and returns one value per row.
  2. Use colMeans(m, na.rm = TRUE) because matrix columns represent the observations within each row.
  3. Use rowMeans(m, na.rm = TRUE) because it is a specialized vectorized function designed for this row reduction. (correct answer)
  4. Use apply(m, 1, mean, na.rm = TRUE) because the general-purpose apply() function handles missing values more reliably than specialized row functions.
Explanation: When working with matrices in R, you'll frequently need to reduce data along a specific dimension — either collapsing rows into single values or columns into single values. The key distinction to internalize is that R provides both specialized functions (like rowMeans and colMeans) and general-purpose tools (like apply), and knowing when to prefer each is a common exam and real-world concern. rowMeans(m, na.rm = TRUE) is the correct choice here. It is a built-in vectorized function written in optimized C code specifically to compute one mean per row while excluding NA values. It returns a named numeric vector with exactly one value per row — precisely what the problem requires. This makes C the answer. Each distractor contains a specific misconception worth understanding. A is flatly wrong: mean(m, na.rm = TRUE) collapses the entire matrix into a single scalar value, completely ignoring row structure. R treats the matrix as one long vector here. B confuses the axis: colMeans() computes one mean per column, not per row — the naming tells you the direction of the output, not the direction of reduction. If you want one value per row, think rowMeans. D is tempting but misleading: apply(m, 1, mean, na.rm = TRUE) does produce correct results, but it is slower and more verbose than rowMeans. The claim that apply handles missing values "more reliably" is false — both handle NA identically when na.rm = TRUE is passed. A useful study tip: in R, prefer named specialized functions (rowMeans, colMeans, rowSums, colSums) over apply whenever they exist — they're faster, cleaner, and signal intent immediately to anyone reading your code.

Question 7

An expensive deterministic function score() is applied to candidates in priority order. The program needs the position of the first candidate whose score exceeds a limit, and candidates after that position should not be evaluated.

Which approach is most appropriate when early candidates frequently exceed the limit?

  1. Evaluate score(candidates) for the entire vector and then select the first qualifying position from the complete result.
  2. Use a loop that evaluates candidates in order and exits with break immediately after the first qualifying score. (correct answer)
  3. Use which.max(score(candidates)) because the largest score necessarily occurs at the first threshold crossing.
  4. Sort the candidates before vectorized evaluation because sorting preserves the original priority position of the first match.
Explanation: When a function is expensive and you only need the first result that meets a condition, the key concept is lazy evaluation through early exit — doing only as much work as necessary and stopping the moment the goal is achieved. A loop with break (choice B) is the right tool here. You evaluate score() one candidate at a time in priority order, and the instant a score exceeds the limit, you record that position and exit the loop immediately. If early candidates frequently exceed the limit, you may only need to call score() once or twice before stopping — dramatically reducing computation compared to any approach that processes the full vector. Choice A fails precisely because it ignores the expensive nature of score(). Calling score(candidates) on the entire vector forces R to evaluate every candidate regardless of when the first qualifying one appears. You do far more work than necessary, which the problem explicitly warns you to avoid. Choice C misunderstands what which.max() does — it finds the position of the largest score, not the first score that crosses a threshold. A candidate with a moderate score that just barely exceeds the limit would be the correct answer, but which.max() would skip it in favor of a higher score elsewhere in the vector. Choice D introduces a subtle trap: sorting the candidates destroys their original priority order. Even if you then find the first qualifying score after sorting, that position no longer corresponds to the priority ranking the problem requires you to respect. Your study tip: whenever a question mentions an "expensive" function alongside an ordered stopping condition, that's a signal to reach for a loop with break rather than any vectorized approach that processes everything upfront.

Question 8

A very large vector x must be transformed into squared deviations from its mean. The direct expression (x - mean(x))^2 is fast on smaller data, but on the production machine it fails because the output plus temporary full-length vectors exceed available memory.

Given this measured memory constraint, which response is most appropriate?

  1. Keep the direct expression because vectorized code generally uses less memory than any equivalent explicit loop.
  2. Grow the output with out <- c(out, value) inside a loop because repeated concatenation minimizes peak allocation.
  3. Compute the mean once, preallocate the output, and fill it in a loop to reduce the number of full-length temporary allocations. (correct answer)
  4. Replace the expression with sapply(x, function(v) (v - mean(x))^2) because sapply() avoids creating intermediate full-length vectors and handles large inputs efficiently.
Explanation: When you see a memory-constraint question in R, shift your thinking from speed to peak allocation — ask how many full-length copies of the data exist simultaneously at any point in execution. The direct expression (x - mean(x))^2 is elegant but memory-hungry: R creates a temporary vector for x - mean(x) and another for the squared result, so you briefly hold two or three full-length vectors alongside the original. When x is massive, that multiplication of allocations is exactly what crashes the job. The fix in C — computing mean(x) once, preallocating out <- numeric(length(x)), then filling element-by-element in a loop — keeps only the output vector and the scalar mean alive. Each iteration overwrites a single slot, so peak memory stays near one full-length vector rather than three. A is a tempting trap because vectorized code is generally faster, but "vectorized" does not mean "memory-minimal." Vectorized operations still create intermediate full-length objects; that's precisely the problem described. B is the worst of all options: growing a vector with c(out, value) inside a loop copies the entire existing vector at each step, causing both quadratic time complexity and escalating memory use — the opposite of what you want. D is doubly flawed: sapply calls mean(x) on the full vector for every single element, an O(n²) disaster in both time and repeated work, and it offers no memory advantage over the direct expression. For exam questions involving large data and memory limits, always prioritize how many full-length copies a solution creates — that number, not vectorization alone, determines peak allocation.

Question 9

Two numeric vectors x and y are given. The required result is a matrix in which each row corresponds to an element of x, each column corresponds to an element of y, and each entry is the row value minus the column value.

Which implementation is generally preferred when the complete matrix is required?

  1. Use outer(x, y, FUN = "-") because it directly applies subtraction to every pair of elements. (correct answer)
  2. Use a preallocated nested loop because operations involving two indices cannot be expressed by vectorized base R functions.
  3. Use x - y because recycling automatically expands both vectors into the required two-dimensional result.
  4. Use x %o% y because the outer-product operator computes pairwise subtraction when its inputs are numeric.
Explanation: When you need to compute every pairwise combination of two vectors — subtracting, adding, or applying any function — think about the outer product family of functions in R. These are designed precisely for creating matrices where each cell represents some operation between one element from each vector. outer(x, y, FUN = "-") is the correct and preferred approach here. It systematically applies the subtraction function to every (x_i, y_j) pair, returning a matrix with length(x) rows and length(y) columns, exactly matching the described requirement. It's readable, general-purpose, and efficient — making A the right answer. B is false and represents a common misconception. Nested loops can solve this problem, but they are slower and more verbose than vectorized alternatives. The claim that "operations involving two indices cannot be expressed by vectorized base R functions" is simply wrong — outer() exists precisely for this purpose. C is a classic recycling trap. x - y does trigger R's recycling rules, but recycling operates within a single vector dimension, not across two dimensions. The result is a vector, not a matrix, and the recycling pattern may not correspond to the intended pairwise subtraction at all. D misrepresents the %o% operator. x %o% y is equivalent to outer(x, y, FUN = "*") — it computes pairwise multiplication, not subtraction. Using it here would produce the wrong values entirely. As a study tip: whenever a question asks about applying an operation to every combination of two vectors in R, outer() with a custom FUN argument should be your first instinct.

Question 10

A programmer standardizes a numeric vector x using scalar values mu and sigma. The current code preallocates z, loops over seq_along(x), assigns (x[i] - mu) / sigma for nonmissing elements, and assigns NA_real_ otherwise. Assume sigma is nonzero.

Which assessment of replacing the loop with z <- (x - mu) / sigma is most accurate?

  1. Keep the loop because vectorized arithmetic cannot preserve missing positions without an explicit is.na() condition.
  2. Use the vectorized expression because scalar arithmetic is applied elementwise and missing values propagate to the same positions. (correct answer)
  3. Keep the loop because subtracting scalar mu from a vector invokes recycling that changes the intended calculation.
  4. Use the vectorized expression only after removing missing values and reinserting them into their original positions.
Explanation: When you see a question about replacing a loop with a vectorized expression in R, ask yourself two things: does the vectorized version produce the same arithmetic, and does it handle edge cases (like NA) the same way? R's vectorized arithmetic applies scalar operations elementwise automatically. Writing (x - mu) / sigma subtracts mu from every element of x and divides by sigma — exactly what the loop does one index at a time. Crucially, R propagates NA values through arithmetic operations by design: any calculation involving NA returns NA. So if x[i] is NA, then x[i] - mu is NA, and NA / sigma is NA. The vectorized expression preserves missing positions without any explicit is.na() check, making B correct. A is wrong because it assumes vectorized arithmetic silently drops or mishandles NA values. In reality, NA propagation is a core feature of R's arithmetic — no special guarding is needed. C is wrong because subtracting a scalar from a vector is standard, intentional recycling in R. The scalar mu is reused for each element, which is exactly the desired behavior — not a distortion of the calculation. D is wrong because it prescribes unnecessary extra work. Removing NAs, computing, and reinserting them would be cumbersome and error-prone, and it's completely unnecessary since propagation handles it automatically. A useful mental rule: in R, if an operation works correctly for one element, vectorization makes it work correctly for all elements — including missing ones — without modification.