R Programming Interview Questions for 7 Years Experience
40 questions with answers for candidates with 7 years of experience — 21 medium, 19 hard.
What interviewers expect: 7 years experience
Seven-year candidates are evaluated as technical leads or managers. Discussions cover analytics strategy, team practices such as code review, testing and style standards, platform choices like Posit Connect or Docker, and balancing R with Python and SQL across teams. Expect case studies on prioritising projects and measuring business impact. Concrete examples of team outcomes are expected.
This set is weighted towards Data Visualization with ggplot2 (7), Reports, Shiny and Production R (5), Data Structures in R (5), Modelling and Machine Learning in R (5).
Team practices and code quality Platform and tooling decisions Business impact measurement Project prioritisation
Solved at this levelStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
✓ 3 questions free • Unlock the rest in seconds
01
You notice database passwords hardcoded in a shared R script. What do you do?
I remove them immediately, inform the owner and ask for the password to be rotated, since it may exist in version history. Then I store credentials outside the code, in a user-level .Renviron file read with Sys.getenv(), the keyring package backed by the operating system's credential store, or the server's secret management, such as environment variables in Posit Connect. I make sure .Renviron is listed in .gitignore and scan the repository history.
02
You receive 60 monthly CSV files with the same structure. How do you combine them into one data frame?
I list the files with list.files(pattern = 'csv$', full.names = TRUE), then read them all at once with readr::read_csv(files, id = 'file'), which also records the source file. Alternatively I use purrr::map() with read_csv and dplyr::bind_rows(). Before combining, I check that column names and types match, because one file with text in a numeric column will fail or coerce the whole column. I then derive the month from the file name for traceability.
03
How do you customise scales, labels and themes in ggplot2?
purrr's map() applies a function to each element of a list or vector and always returns a list. Typed variants return specific types: map_dbl(), map_chr(), map_lgl() and map_int(), and they error if a result has the wrong type, which is safer than sapply(). map2() iterates over two inputs, pmap() over many, and walk() is used for side effects like saving files. Formula shortcuts such as ~ .x * 2 keep code short.
06
Your R model must score new customers every morning for the CRM team. How would you deploy it?
I save the fitted workflow, including the recipe, with saveRDS() or the vetiver package, which versions models and adds monitoring. For batch scoring, a scheduled R script reads new customers from the database, predicts and writes scores back, run by cron, Posit Connect or a container in the cloud. For real-time needs, I expose the model as a REST API with plumber or vetiver in Docker. I log inputs and track drift.
07
Why are dual-axis charts discouraged and how does ggplot2 handle secondary axes?
Dual-axis charts plot two measures with different scales on one chart, and the visual relationship between the lines depends on arbitrary axis ranges, so readers can be misled about correlation. ggplot2 only allows a secondary axis that is a one-to-one transformation of the primary axis through sec_axis(), such as Celsius and Fahrenheit. For two different metrics, I prefer two aligned panels using facets or the patchwork package.
08
Tell me about feedback you received in a code review and what you changed.
Situation: in my first quarter, a senior reviewed my R reporting script. Task: address the review comments. Action: the main feedback was that my 300-line script mixed data loading, cleaning and plotting with repeated code. I split it into small functions, added input checks and moved constants to a config file. Result: the next report took half the time to build, and I now structure every new script this way from the start.
A list-column is a data frame column whose cells are lists, such as nested data frames or fitted models. With tidyr::nest() you group data, for example by region, into a column of sub-data frames, then use purrr::map() to fit a model per region and broom::tidy() to extract coefficients. This keeps data, models and results aligned row by row, which is cleaner than managing separate lists of models and names.
10
How do you use Reduce(), Filter() and Map() in base R?
Simple averages would be biased toward oversampled groups, so I use the survey weights provided with the data. In R, the survey package defines the design with svydesign(ids = ~psu, strata = ~stratum, weights = ~wt, data = df), then svymean(), svytotal() and svyglm() give weighted estimates with correct standard errors that account for clustering and stratification. The srvyr package offers the same with dplyr-style syntax.
12
Salary data has a few extreme values that distort averages. How do you handle them?
First I investigate whether they are data errors, such as an extra zero, or genuine high earners. I flag outliers with the IQR rule, values beyond Q1 - 1.5 IQR or Q3 + 1.5 IQR, or with percentiles. Errors I fix or remove with documentation. Genuine values I keep, but I report the median alongside the mean, or winsorize at the 1st and 99th percentiles for modelling. The choice depends on the business question.
13
A predictor such as pincode has 500 levels. How do you use it in a model?
Dummy-encoding 500 levels creates sparse, noisy features and overfits rare levels. I first lump rare levels into 'other' with step_other(). Then I consider target or effect encoding with the embed package, applied inside cross-validation to avoid leakage, or mapping pincodes to meaningful higher-level features such as city tier, region or distance to a warehouse. Tree models in some engines handle many levels better, but grouping by business meaning usually works best.
plotly's ggplotly() converts an existing ggplot into an interactive chart with hover tooltips, zoom and legend toggling. ggiraph adds interactive tooltips and selection to ggplot geoms with fine control. highcharter and echarts4r wrap JavaScript charting libraries, and leaflet builds interactive maps. These outputs work in R Markdown or Quarto HTML reports and Shiny apps. For static PDF reports, standard ggplot2 is still the better choice.
15
An R script must run every morning at 7 AM and email a report. How do you schedule it?
On a Linux server I schedule Rscript with cron, for example 0 7 * * * Rscript /path/report.R, and on Windows with Task Scheduler or the taskscheduleR package. Posit Connect can schedule Quarto reports and email them directly. GitHub Actions can run scheduled workflows for non-sensitive jobs. The script itself renders the report and sends it with blastula or emayili. I add logging and an alert on failure so silent misses are caught.
The sf package stores spatial data as data frames with a geometry column, and ggplot2's geom_sf() draws them. You read shapefiles or GeoJSON with st_read(), join your data, for example sales by state, and fill polygons by value to make a choropleth. tmap is a dedicated thematic mapping package and leaflet builds interactive web maps. Coordinate reference systems must match, and st_transform() reprojects layers when they do not.
17
A customer table has multiple records per customer. How do you keep only the latest record for each?
I group by customer_id and keep the row with the latest updated_at using slice_max(updated_at, n = 1, with_ties = FALSE), then ungroup. An alternative is arrange(desc(updated_at)) followed by distinct(customer_id, .keep_all = TRUE). I check that updated_at is a proper date-time and has no NAs, since missing timestamps would be dropped or mis-sorted. Finally I confirm that n_distinct(customer_id) equals the number of rows.
18
What is tidy evaluation and why do you need {{ }} in functions?
dplyr uses data masking, so you can write column names without quotes. Inside your own function, a column passed as an argument is not found automatically because R looks for a variable with that name. Embracing the argument with {{ }} tells dplyr to use the column the caller supplied. For column names stored as strings, you use .data[[var]]. For new column names, use the glue syntax '{{var}}_mean' := with the walrus operator.
19
How would you create a reusable branded theme for all team charts?
Combine visual checks with formal tests, because tests become oversensitive with large samples.
hist(x, breaks = 30)
qqnorm(x); qqline(x, col = 'red')
shapiro.test(x) # works for n between 3 and 5000
library(ggplot2)
ggplot(df, aes(sample = x)) +
stat_qq() + stat_qq_line()
# for model residuals
plot(lm_fit, which = 2)
21
A loop calls an external API 5,000 times and crashes at call 3,200. How do you make it robust?
I wrap each call with purrr::safely() or tryCatch() so one failure does not stop the loop, and add retries with exponential backoff, which httr2 supports through req_retry(). I respect rate limits with Sys.sleep() or req_throttle(). I save results incrementally, for example every 100 calls to an RDS file or database, so a restart resumes from the last checkpoint. Finally I log failed IDs and rerun only those.
22
An API returns deeply nested JSON. How do you turn it into a tidy data frame in R?
I make the request with httr2, handling authentication and pagination, then parse with jsonlite::fromJSON(), which simplifies arrays of objects into data frames, using flatten = TRUE to spread nested objects into columns. For deeper nesting I keep a list and use tidyr::unnest_wider() and unnest_longer() to expand list-columns step by step. Optional keys become NA, so I check missing fields across records and validate types before analysis.
Situation: a marketing head expected a new campaign to have doubled sign-ups. Task: evaluate its impact. Action: my analysis in R using a pre-post comparison with a control region showed only a 6 percent lift, much of it seasonal. I presented the method and confidence interval openly, acknowledged what went well and suggested a cleaner randomized test. Result: the stakeholder accepted the finding, and the next campaign was run as a proper experiment.
25
Business users do not trust your gradient boosting model because it is a black box. What do you do?
I explain both global and individual behaviour. Globally, I show variable importance and partial dependence plots, using DALEX or the vip package, to show how key features affect predictions. For individual cases I use SHAP values, for example through the shapviz or fastshap packages, so a manager can see why a customer was flagged. I compare against a simple logistic model and walk users through real examples they know.
26
What are the differences between S3, S4 and R6 classes?
S3 is R's simplest object system: an object is a list with a class attribute, and generic functions like print() dispatch on it through UseMethod(). S4 adds formal class definitions with setClass(), typed slots and validity checks, and is common in Bioconductor. R6, from a package, provides reference semantics with mutable objects and encapsulated methods, similar to classes in Python or Java, and is used inside Shiny. Most analysis code needs only S3.
27
Your function gives correct results when you test it but wrong results in the main script. What could be the cause?
A common cause is that the function reads a variable from the global environment instead of taking it as an argument. During testing that global variable had one value, and in the main script it has another. Lexical scoping finds it silently. I would check the function body for names not defined as arguments or locally, for example using codetools::findGlobals(fn), and pass everything explicitly so the function is self-contained.
28
Tell me about a chart or visualization that changed a business decision.
Situation: the operations team believed late deliveries were spread evenly across the week. Task: I was asked to review delivery data. Action: in R I built a heatmap of late deliveries by weekday and hour with ggplot2, which showed a sharp cluster on Monday mornings linked to weekend order backlog. Result: management added a Sunday evening shift, and late deliveries dropped by about 18 percent the next quarter.
R evaluates function arguments lazily, only when they are first used inside the function body. Arguments are stored as promises holding the expression and its environment. For example, f <- function(x, y) x * 2 works with f(5, stop('error')) because y is never touched. Lazy evaluation enables defaults that depend on other arguments and non-standard evaluation in dplyr, but it can surprise you when creating closures in a loop, which force() fixes.
An environment is a collection of name-value bindings with a pointer to a parent environment. R uses lexical scoping: when a function looks up a variable, it searches its own environment, then the environment where it was defined, up through the global environment and attached packages. So a function defined inside another can read the outer function's variables, which enables closures. The <<- operator modifies a binding in a parent environment and should be used sparingly.
33
What is the difference between Pearson and Spearman correlation?
Pearson correlation measures the strength of a linear relationship between two numeric variables and is sensitive to outliers. Spearman correlation is computed on ranks, so it measures monotonic relationships and is robust to outliers and non-normal data. In R, cor(x, y, method = 'spearman') and cor.test() compute them. For example, ad spend and sales with diminishing returns may show a moderate Pearson value but a high Spearman value.
34
Tell me about a time you automated a manual report.
Situation: my team spent two days every month building a sales report in Excel. Task: I was asked to reduce the effort. Action: I wrote an R script that pulled data from the database with DBI, cleaned it with dplyr, created charts with ggplot2 and rendered a Quarto report scheduled through cron. Result: the report ran in ten minutes, copy-paste errors disappeared and the team used the saved time for deeper analysis.
35
You are building a fraud model where only 0.5 percent of transactions are fraud. How do you handle the imbalance?
Accuracy is useless here, since predicting no fraud gives 99.5 percent. I use stratified splits and evaluate with precision, recall, F1 and the precision-recall AUC. To help the model learn the minority class, I try class weights, downsampling the majority or SMOTE through the themis package in a recipe, applied only to training data. Then I tune the probability threshold based on the cost of missed fraud versus false alarms agreed with the business.
36
Your Shiny dashboard becomes very slow when 50 users open it at once. How do you improve it?
I profile with profvis and reactlog to find slow reactives. I move heavy data loading outside the server function so it runs once per process, pre-aggregate data instead of computing on raw rows, and cache results with bindCache(). I use bindEvent() so expensive work runs only on a button click. Long computations can go to background tasks with ExtendedTask and promises. Finally I scale out with multiple R processes on Posit Connect or Shiny Server Pro.
dbplyr is a dplyr backend that translates dplyr verbs into SQL and runs them inside a database. You connect with DBI, reference a table with tbl(con, 'orders') and write normal dplyr code. Queries are lazy: nothing runs until you call collect(), and show_query() prints the generated SQL. This lets analysts work with large warehouse tables without pulling all rows into R, though some R functions have no SQL translation.
geom_smooth() adds a fitted trend line with a confidence band, using loess for small data and a GAM for large data by default, or method = 'lm' for a straight line. stat_summary() computes summaries of y for each x, for example stat_summary(fun = mean, geom = 'point') to plot group means, or fun.data = mean_cl_normal for error bars. Both let you show patterns without precomputing summaries.
40
Your data is heavily skewed and the sample is small. Can you still use a t-test?
With small, heavily skewed samples the t-test's normality assumption is doubtful. I would check with a histogram, Q-Q plot or shapiro.test(). Options are a non-parametric test, wilcox.test() for two groups or kruskal.test() for more, a log transformation if the data is right-skewed and positive, or a bootstrap confidence interval for the difference in means. With large samples, the Central Limit Theorem usually makes the t-test acceptable.
Frequently asked questions
What is asked in R Programming interviews for 7 years of experience?
Seven-year candidates are evaluated as technical leads or managers. Discussions cover analytics strategy, team practices such as code review, testing and style standards, platform choices like Posit Connect or Docker, and balancing R with Python and SQL across teams. Expect case studies on prioritising projects and measuring business impact. Concrete examples of team outcomes are expected.
How many questions should I practise before a R Programming interview?
Work through all 40 questions here, then the company-wise sets for the employers you are targeting. Quality matters more than count: be able to explain why each answer is right.
Which topics matter most for 7 years of experience?
Focus first on Team practices and code quality, Platform and tooling decisions, Business impact measurement, Project prioritisation.