All questions
Question 1
During review, one programmer replaces a straightforward for loop with lapply(), stating that apply-family functions are vectorized and therefore always faster. Both versions call the same nonvectorized R function once for each list element.
Which response most accurately communicates the tradeoff?
lapply() is necessarily faster because its iteration occurs entirely outside R, so benchmarking would not affect the decision.lapply() may express the operation more clearly, but it does not guarantee a speedup; representative benchmarks should guide any performance claim. (correct answer)- The
for loop is necessarily faster because lapply() must construct a list, so readability is the only reason to consider it. - The two forms must have identical performance because they invoke the same function, making implementation choice purely stylistic.
Explanation: When evaluating performance claims in R, you should always ask: what does the implementation actually do, not just what does it look like? This question tests whether you understand the real relationship between lapply(), for loops, and execution speed.
The key insight is that lapply() is not a vectorized function in the true sense — it's an iteration wrapper. When both a for loop and lapply() call the same non-vectorized R function once per element, the work being done is fundamentally equivalent. lapply() does have a slightly more efficient internal loop (written in C), which can yield modest speedups, but this is never guaranteed — especially when the bottleneck is the R-level function being called, not the loop overhead itself. Answer B is correct because it honestly captures both sides: lapply() can improve readability and may be faster, but performance claims require actual benchmarking with representative data.
Answer A is wrong because it overstates the case. The internal C loop in lapply() doesn't eliminate the R function call overhead, and benchmarking is absolutely still relevant. Answer C swings to the opposite extreme — claiming for loops are necessarily faster because lapply() builds a list. The list-construction cost is usually negligible, and this framing is just as misleading as A's overconfidence. Answer D wrongly assumes identical R-level function calls produce identical total performance, ignoring differences in loop overhead and memory allocation patterns.
Your study tip: whenever you see absolute language like "always," "necessarily," or "must" in performance claims, treat it as a red flag. In R, performance almost always depends on context — benchmark before you commit.
Question 2
A base R data-cleaning pipeline is easy for the team to maintain but takes 30 minutes on production data. A prototype using a specialized package finishes in 8 minutes. The package introduces new syntax, an additional dependency, and reference-style updates that are unfamiliar to the team.
Which recommendation best communicates the relevant tradeoff?
- Retain base R because unfamiliar syntax makes the measured speedup irrelevant until every team member independently approves the package.
- Adopt the package immediately because a production speedup outweighs dependency, mutation, and training concerns in data-processing code.
- Report the measured runtime gain while also documenting dependency, learning, and mutation risks; adopt it only if those costs are acceptable. (correct answer)
- Maintain both implementations permanently because duplicated pipelines eliminate the need to choose between runtime and maintainability.
Explanation: When evaluating a technical tradeoff in software engineering, your goal isn't to pick a winner — it's to identify all relevant costs and benefits, then let informed stakeholders decide. That's exactly what this question is testing.
The strongest recommendation here is C. It acknowledges the concrete, measured benefit (a 22-minute runtime reduction — nearly 4× faster) while honestly cataloging the real costs: an added dependency that could break future environments, reference-style mutation semantics that introduce subtle bugs if misunderstood, and a learning curve that temporarily slows the team. By framing adoption as conditional on whether those costs are acceptable, C respects both the data and the team's context. That's sound engineering communication.
A fails because it hands veto power to individual team members and treats unfamiliarity as a reason to dismiss a measured result. Unfamiliarity is a training cost, not evidence that the speedup is irrelevant. This conflates discomfort with invalidity.
B goes too far in the opposite direction. A runtime improvement is significant, but immediately adopting a package without weighing dependency fragility, mutation risks, and onboarding burden is reckless. Production environments punish unconsidered dependencies.
D sounds balanced but is actually the worst option. Permanently maintaining two implementations doubles maintenance burden, creates consistency risks, and defers a decision indefinitely. It doesn't eliminate the tradeoff — it just avoids confronting it.
A good study tip: when exam questions present a "measured improvement vs. hidden costs" scenario, watch for answers that either dismiss the measurement entirely or ignore the costs entirely. The correct answer almost always quantifies the gain and names the risks, then defers to context.
Question 3
Profiling shows that a well-named helper function accounts for about 2% of a report's runtime. Inlining its body throughout the program makes the code harder to read and reduces total runtime by about 1% in repeated production-like tests.
Which conclusion is most appropriate?
- Keep the helper unless the small end-to-end gain is operationally important, and document that inlining trades clarity for a measured minor improvement. (correct answer)
- Inline the helper because any statistically repeatable speed improvement should take priority over readability in production software.
- Keep the helper because functions accounting for little runtime can never produce a measurable end-to-end performance improvement.
- Inline only half of the call sites because splitting the approaches guarantees most of the speedup without adding maintenance inconsistency.
Explanation: When a question asks you to weigh a performance optimization against code quality, apply the classic "is it worth it?" framework: compare the actual gain against the real costs — maintainability, readability, and future development friction.
Here, profiling reveals the helper contributes ~2% of runtime, and inlining recovers only ~1% end-to-end. That's a measurable but minor gain that comes at a clear cost: reduced readability. The right call is to keep the helper by default, but acknowledge that rare operational contexts (e.g., an extremely latency-sensitive loop running millions of times) might justify the tradeoff — and if you ever do inline, you should document exactly why. That's the reasoning behind A, which correctly frames the decision as conditional on operational importance rather than reflexive.
B is wrong because it overgeneralizes: statistical repeatability doesn't make a result operationally significant. A 1% runtime reduction is real but rarely worth sacrificing maintainability in most production contexts. Prioritizing speed unconditionally is a form of premature optimization.
C is wrong in the opposite direction — it makes an absolute claim ("can never produce a measurable improvement") that contradicts the scenario itself. The improvement was measured; the question is whether it's worth acting on.
D sounds like a clever compromise but introduces the worst of both worlds: inconsistent code style and split behavior that will confuse future maintainers, without guaranteeing the claimed speedup split is proportional or predictable.
As a study tip, watch for optimization questions that tempt you toward absolutes. The correct answer is almost always the conditional, context-aware option — one that weighs tradeoffs rather than declaring a universal rule.
Question 4
An R application repeatedly computes an expensive summary from a large data frame. Caching the summary makes repeated requests fast, but the source data can be modified between requests, and storing summaries for many parameter combinations increases memory use.
Which documentation statement most accurately communicates the caching tradeoff?
- Caching improves repeated requests without affecting correctness, provided each cache entry was originally computed from valid source data.
- Caching should be avoided because memory growth always costs more than recomputing a summary from a large data frame.
- Caching guarantees lower total resource use because reduced computation necessarily compensates for the memory occupied by saved results.
- Caching can reduce repeated computation, but it needs invalidation rules and memory limits to avoid stale results and unbounded storage. (correct answer)
Explanation: When evaluating caching strategies in software, you need to think about both sides of the tradeoff — what you gain (speed) and what you risk (stale data, memory pressure). Questions like this test whether you recognize that caching is a design decision with real costs, not a free optimization.
The most accurate documentation statement is D, because it honestly captures the complete picture. Caching genuinely does reduce repeated computation — that part is valuable. But two concrete problems must be managed: invalidation (ensuring cached results are discarded when source data changes) and memory limits (preventing unbounded growth as parameter combinations multiply). Good documentation warns users about these constraints so they can design around them.
A is tempting but subtly wrong. It claims caching doesn't affect correctness "provided each entry was originally computed from valid data" — but this ignores the staleness problem entirely. If the source data is modified after the cache is populated, the cached summary becomes incorrect even though it was originally valid. The invalidation problem is real and D addresses it; A papers over it.
B overcorrects in the opposite direction, making an absolute claim that memory growth "always" costs more than recomputation. This is false — for large data frames with expensive summaries, caching can absolutely be worth the memory overhead. Blanket avoidance is bad engineering advice.
C is also too absolute. Saying caching "guarantees lower total resource use" ignores scenarios where many cached entries consume significant memory without proportional computation savings — exactly the unbounded storage problem D flags.
When you see caching questions, watch for answers that are one-sided — they either oversell the benefit or overdramatize the cost. The correct answer will acknowledge both.
Question 5
A team rewrites a heavily used numeric loop with Rcpp. Benchmarks on supported systems show a substantial speedup. The new implementation requires a compiler toolchain, manual attention to bounds and types, and separate testing across operating systems.
Which project note best communicates the decision?
- The Rcpp version is preferable because compiled code removes the need for input validation and behaves consistently across all supported platforms.
- The R version is preferable because native extensions cannot provide meaningful speedups when the same algorithm is retained.
- The Rcpp version offers a measured hotspot speedup, but it adds build, portability, and safety costs that require tests and maintenance. (correct answer)
- Both versions should run for every request because comparing their outputs at runtime eliminates build and portability concerns.
Explanation: When evaluating engineering tradeoffs in R, you should think about both what a tool gains you and what it costs you. Rcpp enables C++ compilation within R, which can dramatically speed up numeric bottlenecks — but that benefit comes bundled with real operational complexity.
C is the correct choice because it accurately captures both sides of the tradeoff. The Rcpp version delivers a measured speedup on a specific hotspot, which is honest and precise. But it also acknowledges the genuine costs: you need a compiler toolchain (like Rtools on Windows), you must manage memory bounds and type safety manually, and you must maintain separate test coverage across operating systems. A responsible project note communicates the full picture, not just the win.
A is wrong because it claims compiled code removes the need for input validation — the opposite is true. C++ gives you no automatic bounds checking or type coercion; you must handle these yourself, making validation more critical, not unnecessary. The claim about consistent cross-platform behavior is also overstated.
B is wrong because it asserts Rcpp can't provide meaningful speedups, which contradicts well-established benchmarking evidence. Algorithmic complexity stays the same, but compiled execution reduces constant-factor overhead significantly — especially in tight loops.
D is wrong because running both versions simultaneously on every request doesn't eliminate build and portability concerns — it doubles them. This approach also introduces latency and architectural complexity with no realistic justification.
The key study tip: when a question asks about technical tradeoffs, watch for answers that present only one side. Correct technical decision-making in R (and on this exam) almost always requires acknowledging both the benefit and the cost.
Question 6
Profiling a production workflow shows that a calculation consumes 20% of total runtime. A proposed rewrite makes that calculation four times as fast, with no change to the remaining work. The rewrite is considerably harder to understand.
Assuming the profile is representative, which statement best communicates the expected end-to-end tradeoff?
- The workflow becomes about four times as fast overall, justifying the readability loss based on the hotspot benchmark result alone.
- The workflow's runtime falls by about 15%, so the team must weigh that bounded gain against the added complexity and maintenance cost. (correct answer)
- The workflow's runtime falls by the full 20%, because optimizing the bottleneck eliminates its entire original contribution to elapsed time.
- The workflow cannot improve overall, because the unchanged 80% of work fully determines total elapsed time after the optimization.
Explanation: Whenever you see a question about optimization and performance, reach for Amdahl's Law — the principle that the speedup of a whole system is limited by the fraction of work that can actually be improved. The formula is:
S=(1−p)+kp1
where p is the fraction being optimized and k is the speedup factor. Here, p=0.20 and k=4, giving:
S=0.80+40.201=0.80+0.051=0.851≈1.176
That's roughly a 15% reduction in total runtime — not four times faster, not 20% faster. So B is correct: a bounded ~15% gain that the team must honestly weigh against the real costs of harder-to-read, harder-to-maintain code.
A is the most dangerous distractor — it confuses the local benchmark (4× faster for that section) with the global outcome. Making one piece four times faster does not make the whole workflow four times faster when that piece is only 20% of work. C is a subtler mistake: eliminating a bottleneck's contribution entirely would only save 20% if the calculation dropped to zero time, but here it's merely reduced by 75% of its original 20% (saving 15%). D is the opposite extreme — it wrongly implies optimization is pointless because the unchanged portion dominates. Smaller gains are still real gains.
Your strategy tip: when a question involves partial optimizations, always ask what fraction of total work is affected? A fast improvement on a small slice yields modest system-wide returns — that's the core lesson of Amdahl's Law. Question 7
A simulation currently uses lapply() with a fixed random seed and produces reproducible results. A developer proposes parallel execution. Tests show a large speedup for long simulation batches but a slowdown for short batches.
Which release-note statement best communicates the limitations of the parallel version?
- Parallel execution is faster whenever multiple cores are available, while setting the original seed preserves the exact sequential results automatically.
- Parallel execution improves long batches, but startup and communication overhead can hurt short batches, and reproducibility requires parallel-safe random-number handling. (correct answer)
- Parallel execution improves only short batches because process startup is amortized there, while long batches should retain the sequential implementation.
- Parallel execution changes only runtime, so random-number behavior and platform-specific process support do not need separate documentation.
Explanation: When a question asks about documenting parallel execution trade-offs, you need to think about two distinct concerns: performance characteristics and correctness guarantees. A good release note must accurately describe both — not just one.
The core insight here is that parallelism introduces overhead (spawning worker processes, inter-process communication, result aggregation) that only pays off when the workload is large enough to amortize those costs. The empirical evidence in the passage confirms exactly this: long batches speed up, short batches slow down. Separately, parallel workers cannot simply inherit a single set.seed() call and produce reproducible results — each worker needs its own independent random stream (tools like parallel::clusterSetRNGStream() or L'Ecuyer-CMRG streams exist precisely for this). Answer B captures both truths cleanly, making it the release note that genuinely informs users.
Answer A fails on two counts: it falsely claims parallel is always faster when multiple cores exist, and it wrongly implies the original sequential seed automatically carries over to parallel workers — a dangerous misconception that breaks reproducibility silently.
Answer C gets the performance relationship completely backwards. Process startup overhead hurts short batches, not long ones. Claiming parallelism "improves only short batches" is the opposite of what the passage describes.
Answer D dismisses reproducibility and platform-specific concerns as unnecessary to document — the exact opposite of responsible release communication. Parallel random-number behavior absolutely needs documentation because incorrect assumptions lead to silent correctness bugs.
Your study tip: whenever you see a parallel-vs-sequential trade-off question, mentally check two independent axes — when does it help performance? and what correctness guarantees change? Both must appear in any complete, honest answer.
Question 8
A developer replaces a clear loop with a compact vectorized expression. A microbenchmark on vectors of length 100 shows that the new version is twice as fast. In production, vectors usually contain several million elements, and the surrounding workflow reads data from a database.
Which statement most appropriately communicates the limitation of the performance result?
- The vectorized version should be adopted because a twofold microbenchmark improvement normally transfers to larger production workloads.
- The result establishes faster computation, but database time can be ignored because it is identical for both implementations.
- The benchmark is preliminary because input size and database costs differ from production; end-to-end tests may show a smaller benefit. (correct answer)
- The loop should be retained because vectorized R code generally consumes more memory and is therefore slower on large inputs.
Explanation: When evaluating performance benchmarks in R, you need to think critically about generalizability — does the test reflect real production conditions? A benchmark is only meaningful if its inputs, scale, and environment match what the code will actually face.
The key insight here is that the benchmark was run on vectors of length 100, but production uses several million elements. Computational scaling behavior can change dramatically with input size — an operation that's twice as fast at 100 elements may show a different advantage (or even a disadvantage) at millions of elements due to memory allocation patterns, cache effects, or garbage collection overhead. On top of that, the surrounding workflow involves database reads, which can dwarf any in-memory computation differences entirely. C is correct because it honestly acknowledges both of these gaps: input size differs from production, and the database cost hasn't been factored into the comparison. Recommending end-to-end testing is the responsible next step.
A commits the classic overgeneralization trap — assuming that a microbenchmark result "transfers" to production. This is almost never guaranteed, especially across different scales. B is dangerously misleading: database time absolutely cannot be ignored just because it's "equal" for both versions. If database I/O dominates total runtime, a 2× speedup in computation might reduce end-to-end time by only 1–2%, making the optimization largely irrelevant. D is a false generalization — vectorized R code is typically faster than loops, not slower, and blanket rejection of vectorization based on memory concerns ignores the full picture.
When you see benchmark results in exam questions, always ask: "Do the test conditions actually match production?" If scale, I/O, or environment differ, the result is preliminary — not definitive.
Question 9
A readable function uses a loop to average positive, nonmissing values. A proposed faster replacement is mean(x[x > 0]). The loop explicitly skips NA values, but the replacement has only been benchmarked on complete data.
What is the best code-review comment about the proposed replacement?
- Adopt it because vectorized expressions preserve loop semantics automatically, although the handling of empty inputs should still be documented.
- Reject it because concise expressions are inherently less maintainable than loops, even when tests establish identical behavior.
- Adopt it for complete vectors and silently fall back to the loop whenever the input contains any missing values.
- Do not claim a valid optimization yet; match the documented missing-value semantics and test edge cases before comparing performance. (correct answer)
Explanation: When reviewing a proposed code optimization, your first responsibility is correctness before performance. The central question here is: does the replacement faithfully reproduce the original behavior across all inputs the original handles?
The original loop explicitly skips NA values when computing the mean of positive numbers. The proposed mean(x[x > 0]) has only been tested on complete data — meaning its behavior with NA values is unverified. In R, x[x > 0] applied to a vector containing NA will retain those NA entries in the subset (because NA > 0 evaluates to NA, not FALSE), causing mean() to return NA unless na.rm = TRUE is specified. This is a semantic mismatch. Until the replacement matches documented behavior and edge cases are tested (empty vectors, all-negative inputs, all-NA inputs), no performance comparison is meaningful. D is correct: verify semantics and test edge cases first.
A is wrong because vectorized expressions do not automatically preserve loop semantics — they can behave differently with NAs, empty inputs, or boundary conditions. The premise is false.
B is wrong because it rejects the replacement on a flawed principle. Concise vectorized code is often more maintainable and readable in R; the issue here is unverified correctness, not style.
C is wrong because silently falling back to the loop is a hidden control-flow trap. It masks the unresolved semantic gap rather than fixing it, and "silent" fallbacks are an anti-pattern in code review.
Your study takeaway: in code-review questions, always ask "does the replacement handle all the cases the original does?" before accepting any claimed optimization.
Question 10
A function repeatedly grows a result with out <- c(out, value) inside a long loop. Profiling shows that copying the growing vector dominates runtime. The final number of values can be determined before the loop begins.
Which recommendation best balances performance, readability, and communication of limitations?
- Preallocate the result and fill it by index, explaining that this reduces repeated copying but requires accurate size and index management. (correct answer)
- Keep repeated concatenation because it expresses intent more directly, explaining that R automatically converts it to in-place updates for long vectors.
- Rewrite the loop as a recursive function, explaining that recursion avoids vector copying but may make termination conditions harder to inspect.
- Move the loop into an anonymous
lapply call, explaining that apply-family functions guarantee both preallocation and lower memory use.
Explanation: When you see a question about loop performance in R, the central concept being tested is vector memory allocation. R vectors are immutable under the hood — every time you write out <- c(out, value), R must allocate entirely new memory and copy all existing elements. In a long loop, this creates O(n²) copying behavior, which is why profiling flags it immediately.
A is correct because it directly addresses the root cause: preallocating with something like out <- vector("numeric", n) and filling by index (out[i] <- value) eliminates repeated copying. Crucially, the explanation embedded in choice A is honest — it acknowledges the tradeoff that you must know the final size upfront and carefully manage indices. This transparency makes it the best balance of performance, readability, and honest communication.
B is wrong on a factual basis: R does not automatically convert repeated concatenation into in-place updates for long vectors. This is a common misconception. No such optimization exists in base R — every c(out, value) call copies. This distractor tests whether you believe R has invisible magic optimizations.
C is wrong because rewriting a loop as a recursive function in R typically makes performance worse, not better. R does not optimize tail recursion, and deep recursion risks hitting the call stack limit (Error: C stack usage is too close to the limit). Recursion doesn't solve the copying problem.
D is wrong because lapply does not guarantee lower memory use or automatically preallocate for arbitrary operations — that's a myth about the apply family. lapply returns a list, not a pre-sized vector.
A reliable study tip: whenever a question involves growing data structures in R loops, your first instinct should be "preallocate and index" — it's the canonical, well-supported solution.