R interviews test two things: whether you know the language well enough to avoid its common traps, and whether you can turn data into a clear result. This guide gives you 48 R interview questions and answers, from basics to scenario questions, for freshers and experienced analysts.
Every answer starts with a short, direct response you can say aloud, followed by a table or a code sample with its output. The page covers modern R, with the native pipe, tidyverse, ggplot2, Quarto and tidymodels, and keeps base R where interviewers still ask for it.
The guide has seven parts: 10 on R basics, 9 on functions and the apply family, 8 on data manipulation and the tidyverse, 5 on visualisation and reporting, 5 on statistics and modelling, 6 on advanced R and performance and 5 scenario questions. Use the contents list to jump to your level.
| Section | Questions |
|---|---|
| R Basics (Freshers) | Q1 to Q10 |
| Functions, Control Flow and the Apply Family (Intermediate) | Q11 to Q19 |
| Data Manipulation, Import and Tidyverse (Intermediate) | Q20 to Q27 |
| Visualisation and Reporting (Intermediate) | Q28 to Q32 |
| Statistics and Modelling in R (Intermediate and Experienced) | Q33 to Q37 |
| Advanced R and Performance (Experienced) | Q38 to Q43 |
| Scenario Questions (Experienced) | Q44 to Q48 |
Start with what R is, its data structures and how it handles missing values. Most R interviews begin here, and small details such as recycling and NA are common traps.
R is an open source language and environment for statistical computing, data analysis and graphics. It is used for data cleaning, statistical modelling, visualisation, reporting and increasingly machine learning. Its strength is the package ecosystem on CRAN, which holds more than 20,000 packages, and a very strong community in statistics, research, healthcare and finance.
What the interviewer is listening for: You can name where R is strongest, which is statistics and reporting, without running down other languages.
The atomic types are logical, integer, numeric (double), complex, character and raw. A numeric literal such as 5 is a double by default, and you add an L, as in 5L, to get an integer. Use class() to see the type a user would think of, and typeof() for the storage type.
The main structures are the vector, matrix, array, list, data frame and factor. A vector holds values of one type. A matrix is a two dimensional vector. A list can hold items of different types. A data frame is a list of equal length columns, and a factor stores categories as integer codes with labels.
| Structure | Dimensions | Holds |
|---|---|---|
| Vector | 1 | One type only |
| Matrix | 2 | One type only |
| Array | n | One type only |
| List | 1 | Mixed types, including other lists |
| Data frame | 2 | Columns of different types, equal length |
| Factor | 1 | Categories stored as integers with levels |
A vector holds elements of a single type, so mixing types coerces them to the most general one. A list can hold elements of different types and lengths, including vectors, functions and other lists. Use single brackets to get a sub list and double brackets to extract the element itself.
A data frame is a table where each column is a vector of one type, and columns can differ in type, so it fits real datasets with numbers, text and dates. A matrix has one type for every cell. Data frames are the standard structure for analysis, and the tibble is the tidyverse version.
A factor represents a categorical variable. R stores it as integer codes with a set of labels called levels. Use factors for groups in models and plots, and for ordered categories such as low, medium and high. Since R 4.0, text columns are no longer converted to factors by default.
Levels are sorted alphabetically by default, so high comes before low. Set the levels argument to control the order.
R uses NA for a missing value, and any calculation involving NA returns NA unless you tell it to skip. Most summary functions accept na.rm = TRUE. Test for missing values with is.na(), never with equality, because NA == NA is also NA. NULL is different: it means no object at all.
NA marks a missing value of some type. NaN means not a number and comes from an undefined calculation such as 0 divided by 0. NULL is the empty object with length zero, often returned by functions that find nothing. is.na() is true for both NA and NaN, while is.nan() is true only for NaN.
| Value | Meaning | Length |
|---|---|---|
NA |
Missing value | 1 |
NaN |
Undefined number, such as 0 divided by 0 | 1 |
NULL |
No object at all | 0 |
When two vectors of different lengths are combined in an operation, R repeats the shorter one until it matches the longer one. If the longer length is not a multiple of the shorter, R gives a warning. Recycling is convenient but a common source of silent bugs, so check the lengths.
Computers store decimals in binary floating point, so 0.1 and 0.2 are stored as close approximations, and their sum differs from 0.3 by a tiny amount. Compare numbers with all.equal() wrapped in isTRUE(), which allows a small tolerance, and not with the double equals sign.
These questions test whether you write R the way R is meant to be written: vectorised, with small functions and without unneeded loops.
Use the function keyword, give it arguments with optional defaults, and write the body. R returns the value of the last expression, so an explicit return() is optional. Since R 4.1 you can write short anonymous functions with a backslash, as in \(x) x + 1.
Vectorisation means an operation is applied to every element of a vector at once, without writing a loop. Vectorised code is shorter and faster, because the loop runs in compiled code. Prefer x * 2 to a for loop over x, and use ifelse() instead of an if inside a loop.
The apply function works over the rows or columns of a matrix. The lapply function loops over a list or vector and always returns a list. The sapply function does the same but simplifies the result to a vector or matrix when it can. The vapply function is the safer form, because you state the expected type.
| Function | Works on | Returns |
|---|---|---|
apply |
Matrix rows or columns | Vector, matrix or list |
lapply |
List or vector | Always a list |
sapply |
List or vector | Simplified vector or matrix |
vapply |
List or vector | Vector of a type you declare |
tapply |
Vector grouped by a factor | Array of group results |
mapply |
Several vectors in parallel | Simplified vector or list |
Use a loop when each step depends on the previous result, when you modify an object step by step, or when the logic has side effects such as writing files. Otherwise prefer vectorised code or the apply family. A loop itself is not slow in R. The usual slowness comes from growing an object inside the loop.
Pre allocate the result with vector or numeric of the final length before the loop, and fill it by index.
Use max.col(), which returns the column index of the maximum in each row. Ties are broken at random by default, so set ties.method to first for a repeatable result. The apply function with which.max also works. Note that the function is max.col, and there is no function called col.max.
R evaluates a function argument only when the function first uses it. So an argument that is never used is never evaluated, and a default value can refer to another argument. It enables tools such as substitute() and non standard evaluation, which the tidyverse relies on. It can also delay an error until the argument is used.
Both assign a value at the top level, but the arrow is the convention in R code and works everywhere. The equals sign is used to set function arguments, such as mean(x, na.rm = TRUE). Inside a function call, an arrow creates a variable in the calling environment, so mixing them can surprise you.
An environment is a set of names bound to values. R uses lexical scoping, meaning a function looks for a variable first in its own environment and then in the environment where it was defined, up to the global environment. The double arrow assigns to a variable in an enclosing environment, and is best avoided.
The native pipe, written as |>, arrived in R 4.1 and passes the left side as the first argument of the function on the right. The magrittr pipe, written as %>%, is from a package and allows a dot placeholder and more flexibility. Since R 4.2, the native pipe also supports an underscore placeholder for a named argument.
Most daily R work is cleaning and reshaping data. Know both base R and the tidyverse and be able to say when you use each.
Use read.csv and write.csv in base R, or readr functions such as read_csv for faster, more predictable reading. For Excel use readxl, and for databases use DBI with a driver. Large files read quickly with data.table fread or the arrow package. Always check column types after import.
The core verbs are filter for rows, select for columns, mutate for new columns, arrange for sorting, summarise for collapsing and group_by to apply the others by group. They are chained with a pipe so the code reads in the order of the steps.
In dplyr use left_join, inner_join, right_join, full_join, semi_join and anti_join with a by argument naming the key. In base R use merge(). A left join keeps all rows of the left table, and an anti join returns the rows of the left table with no match, which is useful for finding gaps.
| Join | Keeps |
|---|---|
inner_join |
Only rows with a match in both |
left_join |
All rows from the left, matched rows from the right |
right_join |
All rows from the right, matched rows from the left |
full_join |
All rows from both |
anti_join |
Left rows with no match on the right |
Tidy data has one variable per column, one observation per row and one value per cell. Use pivot_longer from tidyr to turn wide data into long data, and pivot_wider for the reverse. Tidy data makes grouping, plotting and modelling simpler, and the older gather and spread functions are superseded.
First measure it with colSums(is.na(df)) to see the count in each column. Then decide: drop rows with na.omit or tidyr drop_na, fill values with replace_na or a model based imputation, or keep NA where it carries meaning. Record the choice, since each one changes the result.
A data.frame is the base R structure. A tibble is the tidyverse version that prints neatly, never changes column names or types silently and does not use row names. A data.table is a fast, memory efficient extension with a compact syntax, and it suits very large data. They convert easily to each other.
| Type | Strength | Typical use |
|---|---|---|
data.frame |
Built in, universal | Basic analysis and compatibility |
tibble |
Safer printing and subsetting | Tidyverse workflows |
data.table |
Speed and low memory use | Millions of rows |
The Date class holds calendar dates and POSIXct holds date times. Use as.Date with a format, or the lubridate package with functions such as ymd and dmy, which are easier to read. You can add days, extract months and compute the difference between dates directly.
Use distinct() from dplyr to drop fully duplicated rows, or give it columns to deduplicate on, with the keep all argument to retain the other columns. In base R, use df[!duplicated(df), ]. Check duplicates first with count or duplicated, since a duplicate can signal a join or key problem.
Be ready to describe a chart you built and the reason for each choice.
ggplot2 builds a chart in layers using the grammar of graphics. You supply data, map columns to aesthetics such as x, y and colour, add geometries such as points or bars, and then add scales, labels and themes with the plus sign. It makes complex charts consistent and easy to modify.
Base graphics draw directly on a device step by step, which is quick for simple plots but harder to keep consistent. ggplot2 describes the chart as a whole and handles legends, scales and grouping automatically. Most teams use ggplot2 for reports and base plots for quick checks.
Pick by the question. Use a bar chart to compare categories, a line chart for change over time, a histogram or box plot for a distribution, and a scatter plot for the relationship between two numbers. Avoid pie charts with many slices, and label axes clearly.
R Markdown and its successor Quarto combine text, code and output in a single document, which can be rendered to HTML, PDF or Word. They make analysis reproducible, because the report is regenerated from the data and code. Parameters let you produce many reports from one template.
Shiny is an R package for building interactive web applications without writing HTML or JavaScript. An app has a user interface and a server function, and outputs update automatically when inputs change, a pattern called reactivity. Shiny is used for dashboards and internal analysis tools.
Expect to be asked to fit a model and explain the output, not only to name the function.
Use lm() with a formula, and call summary() on the result. The summary shows coefficients with standard errors and p values, the R squared and the residual standard error. A coefficient is the expected change in the outcome for a one unit rise in that predictor, holding the others constant.
Use t.test() with two samples or a formula. It returns the test statistic, degrees of freedom, p value and a confidence interval for the difference. A small p value means the observed difference would be unusual if the true means were equal. Check assumptions of roughly normal data and, for the default, unequal variances.
Use glm() with family = binomial. The coefficients are on the log odds scale, so exponentiate them to get odds ratios. Use predict with type = “response” to get probabilities, and judge the model with a confusion matrix, ROC curve and area under the curve at a sensible threshold.
Split the data into training and test sets, fit on the training set and measure error on the test set. With the tidymodels framework, rsample makes the split and resamples, parsnip defines the model, and yardstick computes metrics. Use cross validation to get a more stable estimate and set a seed so results repeat.
Both give one interface to many modelling methods and help with tuning and resampling. The caret package is older and stable. The tidymodels framework is the newer, modular successor from the same team, built around the tidyverse. New projects usually choose tidymodels, while many existing codebases still use caret.
Experienced candidates are asked about speed, memory and writing code others can maintain.
Measure first with profvis or the bench package, then fix the biggest cost. Vectorise loops, pre allocate results, avoid growing objects, and use data.table or dplyr with a database backend for large data. Move a hot loop to C++ with Rcpp, or run independent tasks in parallel with the future or parallel packages.
R keeps objects in memory, and copies an object when it is modified under some conditions, so large data can run out of memory. Options are to read less data, use data.table or arrow with Parquet files, query a database and bring back only the summary, or use a disk based approach such as duckdb.
They are object systems in R. S3 is informal and based on generic functions and a class attribute, and is the most common. S4 is formal, with defined slots and validity checks. R6 gives reference semantics like classes in other languages, so objects can be changed in place. Choose S3 for simple needs.
Use an R project for each analysis, store code in version control and record package versions with renv, which creates a lockfile that restores the same versions on another machine. Set seeds for random work, avoid absolute paths and keep raw data unchanged. Quarto makes the report rerunnable end to end.
Use usethis and devtools to create the structure, write functions in the R folder with roxygen comments for documentation, add tests with testthat, and check with devtools check. A package makes your code shareable, testable and installable, and it forces a clear description of dependencies.
Use the DBI package with a driver for the database, such as RPostgres or odbc. Write SQL directly, or use dplyr with dbplyr, which translates your pipeline into SQL and runs it in the database. This keeps heavy work in the database and brings back only the results.
Explain how you would approach the problem, what you would check and how you would confirm the answer.
Check what changed: the input data, package versions, the R version, random seeds and the working directory. Compare row counts and a checksum of the input. Restore the exact package versions from the renv lockfile and rerun. Set a seed for any random step. Then add tests so the change is caught next time.
The right table has duplicate keys, so each left row matches several rows. Count the keys with count() to confirm, then deduplicate the right table, add the missing key column, or aggregate before joining. Dplyr gives a warning on many to many joins, so read it. Add a check on row counts after each join.
Write the analysis in a Quarto document with parameters, store data access in a function with credentials held in environment variables, and lock packages with renv. Schedule the render with cron or a scheduler such as Posit Connect, send the output by email or publish it, and add a failure alert and a data check.
Do not read it all at once. Convert it to Parquet, then query it with arrow or duckdb, which process data in chunks and push filters down to the file. Select only the needed columns, aggregate in the engine and collect only the summary into R. Sample the data first to develop the code.
State the finding in plain words, with the effect size and its uncertainty, for example that each extra year of experience is linked to about this much higher salary, give or take a range. Say what the model cannot show, such as cause. Use one clear chart in place of a table of coefficients.
What the interviewer is listening for: You focus on the decision, give a range and not just one number, and you mention the limits.
Prepare data structures, missing values, vectorisation and the apply family first. Then learn dplyr and tidyr, ggplot2, joins, dates and importing data. Add regression, t tests and logistic regression, and reporting with Quarto. Experienced roles add performance, packages and reproducibility.
Learn the one your target employers use. R is strong in statistics, research, healthcare and reporting. Python is stronger for engineering and machine learning pipelines. Many analysts know both, and the ideas such as tidy data and model evaluation transfer between them. Check the job descriptions you are aiming for.
Use the current R release, which is 4.6.1 at the time of writing, and RStudio or Positron as the editor. Install packages from CRAN, and use Quarto for reports. Check the official R and Posit pages for the latest versions.
Learn both. Interviewers often ask base R questions such as apply, subsetting and factors, while daily work uses the tidyverse. Being able to read old base R code is valuable, and knowing the tidyverse makes your own code easier to read.
Use public datasets, such as government open data, and build a small analysis from import to a report. Publish the Quarto output with the code on GitHub, since interviewers ask to see it. Redo one analysis in a second way to practise the alternative approach.
Use this plan if you have about a month before your interview.
| Week | Focus | What to do |
|---|---|---|
| Week 1 | R basics | Practise vectors, lists, data frames, factors, NA and recycling in short scripts. |
| Week 2 | Data manipulation | Clean a real dataset with dplyr and tidyr, join two tables and reshape one. |
| Week 3 | Visualisation and modelling | Build five ggplot2 charts and fit a linear and a logistic regression, explaining each output. |
| Week 4 | Advanced and scenarios | Profile slow code, set up renv and practise the scenario questions aloud. |
If you want to practise these questions with a trainer and work on hands on projects, you can book a free demo class for the Prwatech Data Science course. It covers R, statistics, machine learning and hands on projects.
Contact the learning team for current batch dates and fees.
Written by Mithun Ray, data engineering trainer at Prwatech. Reviewed by Avishekh Verma. Last updated October,2026.