R Programming Interview Questions
Reviewed by Mark Dickie · Last updated
12 questions on this page you can answer and see marked on the spot. Go to the first one
R is a programming language and environment built for statistical computing and graphics. Interviews on R usually test your grip on core data structures (vectors, lists, data frames, factors), the tidyverse toolchain for data wrangling, and base R's functional programming family like apply, lapply, and sapply. You should also be ready to explain scoping rules, memory behavior, and how R differs from Python for analytics workflows.
What does an R interview typically cover?
| Topic | What gets tested | Common question types |
|---|---|---|
| Data structures | Vector vs list vs data frame vs matrix | Type coercion, indexing, subsetting |
| Tidyverse | dplyr verbs, pipe operator, tidyr pivoting | Rewrite base R in dplyr, spot the bug |
| Apply family | apply, lapply, sapply, mapply, purrr::map | Pick the right function for a given task |
| Visualization | ggplot2 grammar, geoms, aesthetics, facets | Build or debug a plot from a spec |
| Statistics | lm, glm, hypothesis testing, distributions | Interpret model output, check assumptions |
| Performance | Vectorization, memory, Rcpp, parallelization | Speed up slow code, profile a bottleneck |
How should I prepare for an R coding interview?
- Be fluent with the five core data structures and know which operations coerce types silently.
- Practice rewriting base R loops with dplyr verbs and the apply family. Interviewers often ask for both forms.
- Understand R's lexical scoping and environment chains, since closures and function factories come up in senior-level rounds.
- Review statistical model output: read a
summary(lm())call, explain coefficients, R-squared, and residual diagnostics. - Know where R is slow and how to fix it: vectorize, pre-allocate, or drop into Rcpp when a loop is unavoidable.
Key facts
- Tarmac has 121 R interview questions on this topic, 25 of them on this page, at difficulty 1–5 of 5.
- Tarmac tracked 195 job postings asking for R in September 2026.
- Roles asking for R advertise a median base salary of A$118,270, across 36 job postings as of September 2026.
- Tarmac last reviewed these R interview questions on 20 October 2026.
At a glance
| Questions | 25 shown · 121 in the bank |
|---|---|
| Difficulty | 1–5 of 5 |
| Formats | True / false, Multiple choice, Find the bug, Ordering, Short answer, Fill in the blank, Multiple answer, Code output, Flashcard |
What you'll review
- r purrr
- r apply lapply sapply
- r control flow
- r vectorization
- r function writing
Practice questions
Try one before you open the answer. Pick an option and press Check; it's marked on the spot.
In R, lapply(X, FUN) always returns a list, regardless of whether FUN returns a scalar, a vector, or another list for each element of X.#
Options
Show answer
True. lapply in R always returns a list of the same length as the input, with no simplification of individual elements. This is a defining property that separates it from sapply, which tries to simplify the output into a vector or matrix.
lapply is defined to return a list of the same length as X. Each element of the result is the return value of FUN applied to the corresponding element of X, and no simplification is performed. This is what distinguishes it from sapply, which attempts to simplify the result to a vector or matrix.
In the purrr package, map_dbl(.x, .f) returns a list, just like map(.x, .f) does.#
Options
Show answer
False. purrr::map_dbl() always returns a numeric (double) vector, not a list. The _dbl suffix guarantees a type-stable atomic vector output, whereas purrr::map() returns a list. This type-stability is a key advantage of the map_* family over base R's sapply.
purrr::map() returns a list, but purrr::map_dbl() is a type-stable variant that always returns a numeric (double) vector of the same length as .x. The _dbl suffix signals the return type, and the function will error if .f produces non-numeric output for any element. This type-stability is the main reason to prefer map_dbl over sapply.
In base R, what type of object does lapply() always return, regardless of the input or the function applied?#
Options
Show answer
lapply() always returns a list. The function applies a given function to each element of a list (or vector coerced to a list) and collects every result into a list, regardless of the result types. No automatic simplification of the output is performed.
The 'l' in lapply stands for 'list'. lapply() iterates over a list (or an object coerced to a list), applies a function to each element, and always collects the results into a list — even when every result is a scalar. No simplification of the output structure is performed.
How does sapply() differ from lapply() in its default behavior?#
Options
Show answer
sapply() is a user-friendly wrapper around lapply() that simplifies the result to a vector or matrix when possible. lapply() always returns a list. If simplification fails (e.g., results of unequal length), sapply() also falls back to returning a list. The underlying iteration logic is identical.
sapply() is a wrapper around lapply() that by default attempts to simplify the output: if each result is a scalar, it returns a vector; if each result is a vector of equal length, it returns a matrix. When simplification is not possible it falls back to a list, just like lapply(). The other options describe apply(), mclapply(), or fabricated restrictions.
What is the result of the following R code?#
Options
Show answer
The code returns the numeric vector c(6, 15). sapply() applies sum() to each list element (6 and 15) and, because each result is a scalar, simplifies the output into a named numeric vector. Using lapply() instead would yield the list list(6, 15).
sapply() applies sum() to each element of the list: sum(1:3) = 6 and sum(4:6) = 15. Because each result is a scalar of the same type, sapply() simplifies the output into a numeric vector c(6, 15). If lapply() were used instead, the result would be the list list(6, 15).
The code below is intended to print whether x equals 5, but it always prints "x is five" regardless of the original value of x. Find the bug.#
x <- 7
if (x <- 5) {
print("x is five")
} else {
print("x is not five")
}Show answer
The bug is on line 2.
Line 2 uses <- (the assignment operator) instead of == (the equality comparison operator). x <- 5 assigns the value 5 to x and evaluates to 5, which is truthy in R, so the if condition is always TRUE no matter what x was before. Changing <- to == makes the code compare x to 5 as intended.
The function below should classify a number as "positive", "negative", or "zero", but classify(7) incorrectly returns "zero". Identify the buggy line.#
classify <- function(n) {
if (n > 0) {
result <- "positive"
}
if (n < 0) {
result <- "negative"
} else {
result <- "zero"
}
return(result)
}Options
Show answer
Line 6: if should be else if so the branches are mutually exclusive.
Line 6 uses a standalone if instead of else if. When n > 0, the first block sets result to "positive", but execution then falls through to the second independent if (n < 0) test. That test is FALSE, so the else branch overwrites result to "zero". Using else if on line 6 makes the three branches mutually exclusive, so classify(7) correctly returns "positive".
In R's implementation of sapply, the function is defined as sapply <- function(X, FUN, ..., simplify = TRUE) { FUN <- match.fun(FUN); answer <- lapply(X, FUN, ...); if(simplify) simplify2array(answer) else answer }. Order the steps that sapply(1:3, function(x) x^2) performs, from first to last.#
Put these in order
Show answer
R's sapply(1:3, function(x) x^2) first evaluates the argument 1:3 to get the iteration vector, then calls lapply internally to apply the function to each element and collect results into a list, and finally calls simplify2array to convert that list into the numeric vector c(1, 4, 9). The correct order is: evaluate argument, run lapply, then simplify.
R's sapply first evaluates its arguments, producing the vector 1:3 as X. It then passes X and the function to lapply, which iterates over the vector and applies the function to each element, storing each return value in a list. Finally, sapply calls simplify2array on that list; because every element is a length-1 numeric, simplify2array produces the atomic numeric vector c(1, 4, 9).
Order these R function calls by the number of dimensions of their primary input data structure, from fewest dimensions to most.#
Put these in order
Show answer
The correct order is: sapply(1:5, sqrt) (1-D vector), then apply(matrix(...), 2, sum) (2-D matrix), then apply(array(...), 3, sum) (3-D array). sapply/lapply work on flat vectors or lists, while apply is designed for multi-dimensional matrices and arrays whose MARGIN argument selects which dimension to iterate along.
sapply operates on a one-dimensional structure — an atomic vector or list. apply is designed for matrices and arrays: the second call passes a 2-D matrix (2 rows × 3 columns), and the third call passes a 3-D array (2 × 3 × 4). The dimensionality strictly increases: 1-D → 2-D → 3-D.
The following R function is intended to add two numbers and return their sum, but it does not work correctly. On which line is the bug?#
add_two <- function(x, y) {
result <- x + y
return(x)
}Show answer
The bug is on line 3.
Line 2 correctly computes the sum x + y and stores it in result. However, line 3 returns x instead of result, so the function always returns its first argument rather than the sum. The fix is to change return(x) to return(result).
In base R, what data structure does sapply(list(1:2, 3:5), sum) return, and what are its element values? (Assume no names are attached to the input list.)#
Show answer
sapply simplifies the list result into a numeric (integer) vector: c(3, 8). Because each element of the result has length 1, sapply returns an atomic vector rather than a list.
Each input element is summed: sum(1:2) = 3 and sum(3:5) = 8. Because every result is a length-1 scalar, sapply's default simplify = TRUE collapses the list into an unnamed numeric vector of length 2: c(3, 8). If the lengths differed sapply could fall back to a matrix or list.
In the purrr package, what is the key behavioral difference between map() and map_dbl() in terms of the object each returns?#
Show answer
map() always returns a list, while map_dbl() returns an atomic numeric (double) vector — it attempts to coerce each element to type double and errors if that coercion fails.
purrr's map family shares the same iteration logic; the suffix determines the output type. map() is type-stable and always returns a list. map_dbl() is a typed variant that simplifies each result to a double and returns an atomic double vector of the same length as the input, raising an error if any element cannot be coerced to double.
In base R, mapply and Map are closely related multivariate apply functions. State the one default argument value that differs between them and describe the consequence for the returned object.#
Show answer
mapply defaults SIMPLIFY = TRUE, so it may simplify results into a vector or matrix. Map defaults SIMPLIFY = FALSE, so it always returns a list.
Map is essentially a wrapper around mapply with SIMPLIFY set to FALSE. This means Map never simplifies — it consistently returns a list of the same length as the longest input. mapply, by contrast, has SIMPLIFY = TRUE by default and will collapse results into an atomic vector or matrix when the shapes allow it, making its return type less predictable.
In R, inside a for or while loop, the _____ statement skips the remaining code in the current iteration and immediately jumps to the next iteration, while the _____ statement exits the loop entirely.#
Show answer
In R, inside a for or while loop, the **next** statement skips the remaining code in the current iteration and immediately jumps to the next iteration, while the **break** statement exits the loop entirely.
R's next keyword is analogous to continue in many other languages: it halts the current iteration and moves to the next one. break terminates the loop altogether, regardless of the loop condition or remaining iterations.
In an R script, placing the else keyword on a new line after the closing brace } of an if block produces a syntax error, because R considers the if statement complete when the line ends. To avoid the error, else must appear on the _____ line as the closing } of the if block.#
Show answer
In an R script, placing the else keyword on a new line after the closing brace } of an if block produces a syntax error, because R considers the if statement complete when the line ends. To avoid the error, else must appear on the same line as the closing } of the if block.
R's parser reads input line by line. When the closing brace of an if block ends a line with no else following it, the parser treats the if statement as finished. A standalone else on the next line is then unexpected, causing a syntax error. Placing else on the same line as the closing brace tells the parser the statement continues.
In R, switch(EXPR, ...) dispatches based on the value of EXPR. When EXPR is a character string, switch() matches it against the _____ of the subsequent named arguments and returns the value associated with the matching name. If no name matches and no unnamed trailing default argument is supplied, switch() returns _____.#
Show answer
In R, switch(EXPR, ...) dispatches based on the value of EXPR. When EXPR is a character string, switch() matches it against the names of the subsequent named arguments and returns the value associated with the matching name. If no name matches and no unnamed trailing default argument is supplied, switch() returns NULL.
For character-mode switch(), R compares EXPR against the names of the trailing arguments. The value bound to the first matching name is returned. If none of the names match and no unnamed default argument is supplied, switch() returns NULL. If an unnamed trailing argument is present, that default value is returned instead.
Consider the following statements about lapply and sapply in R. Which are TRUE? (Select all that apply.)#
Options
Pick every one that applies.
Show answer
lapply always returns a list no matter the input or result shape, while sapply wraps lapply and tries to simplify the output to a vector or matrix, falling back to a list when simplification is not possible. Because data frames are internally lists of columns, lapply on a data frame iterates over columns. sapply does not always return a vector — it can return a matrix or a list.
lapply always returns a list — its name stands for 'list apply' and the return type is guaranteed regardless of input or result shape (the first statement is true). sapply is a user-friendly wrapper that calls lapply and then attempts to simplify the list to a vector or matrix via simplify2array; when simplification fails it falls back to a list, so the claim that sapply always returns a vector is false and the claim that sapply is a wrapper around lapply that attempts to simplify the result to a vector or matrix when possible is true. Data frames in R are lists under the hood (each column is a list element), so lapply on a data frame iterates over columns (the last statement is true).
Given the R code:#
Options
Pick every one that applies.
Show answer
With m <- matrix(1:6, nrow = 2), apply(m, 1, sum) sums each row and returns a named vector of length 2 (values 9 and 12), while apply(m, 2, sum) sums each column and returns a named vector of length 3 (values 3, 7, and 11). The two results have different lengths because MARGIN = 1 operates per-row and MARGIN = 2 operates per-column.
apply with MARGIN = 1 iterates over rows. Row 1 is 1+3+5 = 9, row 2 is 2+4+6 = 12, so the result is a named vector of length 2 (the statement that apply(m, 1, sum) returns a named numeric vector of length 2 is true, the statement that it returns a named numeric vector of length 3 is false). apply with MARGIN = 2 iterates over columns: 1+2 = 3, 3+4 = 7, 5+6 = 11, giving a named vector of length 3 (the statement that apply(m, 2, sum) returns a named numeric vector of length 3 is true). Since one returns length 2 and the other length 3, they are different lengths (the statement that they return different length vectors is true).
What is the exact output of the following R code?#
mat <- matrix(1:12, nrow = 3, byrow = TRUE)
print(apply(mat, 1, function(x) sum(x^2)))Show answer
[1] 30 174 446
matrix(1:12, nrow=3, byrow=TRUE) fills row-wise producing rows (1,2,3,4), (5,6,7,8), (9,10,11,12). apply(mat, 1, ...) iterates over rows (MARGIN=1). For each row it computes the sum of squares: row 1 → 1+4+9+16=30; row 2 → 25+36+49+64=174; row 3 → 81+100+121+144=446. The result is a named (by row index) numeric vector printed as 30 174 446.
mapply(FUN, ..., SIMPLIFY = TRUE) and purrr::map2(.x, .y, .f) both iterate in parallel over two inputs. But purrr::invoke_map(.f, .x) was deprecated in favor of a different approach. What replaced it and what was the core problem with invoke_map's design that motivated the deprecation?#
Show answer
invoke_map(.f, .x) is deprecated in favor of calling rlang::exec() inside a standard map variant, e.g. map2(.f_list, .args_list, ~ rlang::exec(.x, !!!.y)). The purrr team concluded that mixing a list of function names (or formula objects) with a parallel list of argument lists was error-prone and hard to type-check. Using exec() within map2() makes the dispatch explicit, composes naturally with other map variants, and benefits from rlang's consistent tidy-evaluation semantics rather than the bespoke dispatch logic that invoke_map maintained.
These flashcards target senior-level understanding of the R apply/purrr ecosystem: type-safety guarantees of vapply vs sapply, the strict-return contract of typed map_* functions versus sapply's silent coercion, and knowledge of purrr API evolution (invoke_map deprecation in favor of exec()-based patterns such as map2(.f_list, .args_list, ~ rlang::exec(.x, !!!.y))).
Base R's sapply uses an internal helper to decide how to simplify results. What is this function, and what does its higher argument control when simplifying a list of matrices?#
Show answer
simplify2array(x, higher = TRUE) is the internal engine that sapply() and mapply(SIMPLIFY = TRUE) use to collapse a list into a vector/matrix/array. When higher = FALSE, it only simplifies to a vector or matrix; when higher = TRUE (sapply's default), it will attempt to produce higher-dimensional arrays if all list elements are arrays of the same dim. For instance, a list of 3 matrices each 2×2 simplifies to a 2×2×3 array with higher=TRUE but remains a 4×3 matrix (each matrix unrolled) with higher=FALSE. Knowing this lets you predict sapply's exact output shape and explains why results sometimes gain an unexpected dimension.
These flashcards test senior-level knowledge: the precise relationship between mapply/Map via SIMPLIFY, the side-effect design contract of purrr::walk versus map, and the internal simplify2array mechanism that governs sapply's often-surprising output shapes including the higher argument's effect on array dimensionality.
Consider the following R code that combines short-circuit evaluation (&& / ||), the <<- super-assignment operator, and if-based control flow. What is the exact output printed to the console when this code is run?#
f <- function() {
log <- character(0)
check <- function(label, val) {
log <<- c(log, label)
val
}
if (check("A", FALSE) && check("B", TRUE)) {
log <<- c(log, "X")
}
if (check("C", TRUE) || check("D", FALSE)) {
log <<- c(log, "Y")
}
paste(log, collapse = "")
}
cat(f())Show answer
ACY
The first if evaluates check("A", FALSE), which appends "A" to log (via <<- into the enclosing f scope) and returns FALSE. Because && is short-circuit, check("B", TRUE) is never evaluated, so "B" is never appended. The overall condition is FALSE, so "X" is not appended. At this point log is c("A").
The second if evaluates check("C", TRUE), which appends "C" to log and returns TRUE. Because || is short-circuit and the left operand is already TRUE, check("D", FALSE) is never evaluated, so "D" is never appended. The condition is TRUE, so "Y" is appended. Now log is c("A", "C", "Y").
paste(log, collapse = "") yields the string "ACY", and cat prints it without quotes or a trailing newline.
Consider the following data frame in R (base R, version 4.x):#
Options
Pick every one that applies.
Show answer
The true statements are that lapply(df, class) returns list(x = "integer", y = "character"), sapply(df, class) returns the named vector c(x = "integer", y = "character"), and vapply(df, class, character(1)) returns the same named vector with type safety. The key trap is apply(df, 2, class): apply coerces the data frame to a matrix first, unifying all columns to character, so it returns c("character", "character") rather than preserving the original column types.
A data frame is internally a list of columns, so lapply and sapply iterate over columns directly, preserving each column's original type. lapply(df, class) returns list(x = "integer", y = "character") (the first statement is true), and sapply(df, class) simplifies this to the named character vector c(x = "integer", y = "character") (the second statement is true). vapply(df, class, character(1)) does the same as sapply here but enforces that each result is a single character value, returning the identical named vector (the fourth statement is true). The third statement is false: apply first coerces the data frame to a matrix via as.matrix; because the columns have mixed types (integer and character), every element becomes character, so apply(df, 2, class) returns c(x = "character", y = "character"), not c(x = "integer", y = "character"). The fifth statement is false: apply(df, 1, class) iterates over 3 rows (not 2), each of which is a character vector in the coerced matrix, so it returns a character vector of length 3.
In R 4.x, calling sapply(1:3, function(x) NULL) returns list() — an empty list — rather than NULL or an empty atomic vector. This is because sapply internally calls simplify2array on the list of NULL values returned by lapply, and simplify2array collapses a list whose every element has length zero into an empty list.#
Options
Show answer
Yes — sapply(1:3, function(x) NULL) returns list() in R. sapply wraps lapply and then calls simplify2array on the result. When every element is NULL (length zero), simplify2array cannot form a vector or array, so it collapses the list to an empty list(), not NULL or an empty atomic vector.
sapply is a wrapper: it calls lapply, then—if simplify is not FALSE and the result is non-empty—passes the result to simplify2array(answer, higher = (simplify == "array")). For 1:3, lapply produces list(NULL, NULL, NULL). Every element has length 0. simplify2array cannot build a vector or array from zero-length elements, so it returns list(). Hence sapply(1:3, function(x) NULL) evaluates to list(), not NULL or c(). The statement is TRUE.
What is the exact output (including any trailing spaces, but ignoring the leading prompt characters) printed to the console when the following R code is executed?#
f <- function() {
on.exit(cat("F "))
g <- function() {
on.exit(cat("G "))
h <- function() {
on.exit(cat("H "), add = TRUE)
on.exit(cat("H2 "), add = TRUE)
stop("boom")
}
h()
}
tryCatch(g(), error = function(e) cat("E "))
cat("D ")
}
f()Show answer
H2 H G E D F
When h() calls stop("boom"), the error begins stack unwinding. Before the error propagates, each function's on.exit expressions fire from the innermost frame outward. Inside h, two on.exit calls were made with add = TRUE and the default after = FALSE; this means the second expression (cat("H2 ")) is prepended before the first (cat("H ")), so they fire in the order H2 then H. Next, g's on.exit fires (G). The error then reaches the tryCatch in f, which catches it and runs the error handler (E). Because the error was caught, f continues normally and executes cat("D ") (D). When f returns normally, its on.exit fires (F). The full concatenated output is H2 H G E D F .
Related interview questions
Job market
See r salaries and hiring demand from live job postings.
The other 96 questions
This page shows 25 and marks what you pick. That's as far as a page can go. A free account opens the other 96 and keeps every answer. What you miss comes back until it's right: after a day, then at longer gaps.
Free · the whole bank · 100 marked answers per 30 days · written feedback on the paid plan