Skip to content

R Programming Technical Round Questions & Answers

40 questions for the technical round stage — 2 easy, 34 medium, 4 hard.

About the technical round round

The technical round is the core of a R Programming interview: concept questions with follow-ups, a live hands-on task and a deep dive into one of your projects. Interviewers check depth, not just definitions.

Tips for the technical round round

  • Say your approach out loud before you start writing.
  • After a correct answer, expect 'why?' and 'what if?' follow-ups; go one level deeper than the question.
  • Pick one project you can explain end to end, including what you would do differently.
  • If you get stuck, explain what you would try next instead of going silent.
Solved — technical roundStart 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

How do you interpret the summary output of an lm() model?

Save to ▾
Show answer
Each coefficient is the expected change in the outcome for a one-unit increase in that predictor, holding others constant. The p-value tests whether the coefficient differs from zero. R-squared is the share of variance explained, and adjusted R-squared penalizes extra predictors. Residual standard error shows typical prediction error in outcome units. The F-statistic tests whether the model beats an intercept-only model. For factors, coefficients are differences from the reference level.
02

How do you apply the same transformation to many columns in dplyr?

Save to ▾
Show answer
across() applies one or more functions to a set of columns selected with tidyselect helpers. df %>% mutate(across(where(is.character), str_trim)) %>% mutate(across(starts_with('amt_'), ~ round(.x, 2))) df %>% group_by(region) %>% summarise(across(c(sales, profit), list(mean = mean, max = max), .names = '{.col}_{.fn}'))
03

How are dates and times handled in R?

Save to ▾
Show answer
Base R stores dates as the Date class, counting days since 1970-01-01, and date-times as POSIXct, counting seconds. as.Date('2026-03-15') parses ISO strings and format() controls output. The lubridate package simplifies parsing with ymd(), dmy() and mdy(), extracting parts with month() and wday(), and arithmetic like date + months(1). Time zones matter: Indian data usually uses 'Asia/Kolkata', and mixing UTC with IST can shift records across days.
04

Explain environments and lexical scoping in R.

Save to ▾
Show answer
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.
05

Your scatter plot of 200,000 points looks like a solid blob. How do you fix it?

Save to ▾
Show answer
This is overplotting. I reduce point size and add transparency with geom_point(alpha = 0.1, size = 0.5). Better options are geom_hex() or geom_bin2d(), which show density as colour, or geom_density_2d() contours. I could also plot a random sample for shape and a smoothed trend with geom_smooth() on the full data. If x values are discrete, geom_jitter() spreads points. The goal is to make density visible, not every point.
06

How do you check whether data is normally distributed in R?

Save to ▾
Show answer
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)
07

After merging two data frames you get more rows than expected. What happened?

Save to ▾
Show answer
Extra rows after a join usually mean the key is duplicated in one or both tables, so each match multiplies rows. I check uniqueness with df %>% count(key) %>% filter(n > 1) on both sides. Then I decide whether to deduplicate, aggregate before joining or join on a composite key such as customer_id plus month. In dplyr 1.1 and later, the relationship argument, for example 'many-to-one', makes the join fail loudly when this assumption breaks.
08

How do you create interactive charts in R?

Save to ▾
Show answer
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.
09

What is a tibble and how does it differ from a base data frame?

Save to ▾
Show answer
A tibble is the tidyverse version of a data frame. It prints only the rows and columns that fit the screen along with column types, never converts strings to factors, never changes column names, does not partially match with $ and always returns a tibble when subsetting with [ ]. It also supports list-columns easily. You convert with as_tibble(). Tibbles prevent many silent bugs, though some older packages expect base data frames.
10

How do you handle errors in R code?

Save to ▾
Show answer
tryCatch() catches errors and warnings so a script can recover or log them; finally runs cleanup. read_safe <- function(path) { tryCatch({ readr::read_csv(path, show_col_types = FALSE) }, warning = function(w) { message('Warning in ', path, ': ', conditionMessage(w)) NULL }, error = function(e) { message('Failed: ', path, ' - ', conditionMessage(e)) NULL }, finally = message('Done: ', path)) }
11

How do you create a column with multiple conditions in dplyr?

Save to ▾
Show answer
case_when() evaluates conditions in order and returns the value of the first one that is TRUE; .default handles the rest. customers <- customers %>% mutate(segment = case_when( spend >= 50000 ~ 'Platinum', spend >= 20000 ~ 'Gold', spend >= 5000 & visits > 5 ~ 'Silver', .default = 'Regular' )) # simple two-way split mutate(df, high_value = if_else(spend > 20000, 'Yes', 'No'))
12

What does copy-on-modify mean in R and how does it affect performance?

Save to ▾
Show answer
When you assign y <- x, both names point to the same object, and R makes a copy only when one of them is modified. tracemem() shows when copies happen. This is why growing a vector in a loop with x <- c(x, value) is slow, because each step can copy the whole vector. Preallocating with vector('numeric', n), using vectorized functions or data.table's modify-by-reference avoids repeated copies and memory spikes.
13

How would you impute missing values differently for each group?

Save to ▾
Show answer
I group by the relevant category, such as product category, and replace NAs with that group's median: group_by(category) %>% mutate(price = coalesce(price, median(price, na.rm = TRUE))) %>% ungroup(). Group medians are more realistic than a global value. I also add a flag column marking imputed rows so models and reviewers can see them. If a whole group is missing, I fall back to the overall median, and for important features I consider model-based imputation.
14

What is plumber in R?

Save to ▾
Show answer
plumber turns R functions into REST API endpoints using special comments. You annotate a function with #* @get /predict and its arguments become query parameters, then run the API with pr_run(). Other systems, such as a Java web app or a Python service, can call the R model over HTTP and receive JSON. It is commonly used to serve model predictions, and it can be deployed in Docker or on Posit Connect.
15

How do you customise scales, labels and themes in ggplot2?

Save to ▾
Show answer
Scales control how data maps to visuals, and themes control non-data elements. p + scale_y_continuous(labels = scales::label_percent()) + scale_fill_brewer(palette = 'Set2') + labs(title = 'Conversion by Channel', subtitle = 'FY 2025-26', caption = 'Source: CRM') + theme_minimal(base_size = 12) + theme(legend.position = 'bottom', plot.title = element_text(face = 'bold'), panel.grid.minor = element_blank())
16

What is the difference between reactive(), observe() and observeEvent() in Shiny?

Save to ▾
Show answer
reactive() creates a cached value that other code can read, such as filtered data, and it is lazy, recomputing only when needed. observe() runs code for its side effects whenever any reactive dependency changes, returning nothing. observeEvent() runs side effects only when a specific event fires, such as a button click, ignoring other dependencies. Use reactive() for computing values and observeEvent() for actions like saving data or showing notifications.
17

What are semi_join() and anti_join() used for?

Save to ▾
Show answer
They are filtering joins: they keep rows of the left table based on matches in the right table but add no columns. semi_join() keeps rows that have a match, and anti_join() keeps rows that have no match. For example, anti_join(customers, orders, by = 'customer_id') lists customers who never ordered. They are also safer than inner joins for filtering, since they never duplicate rows when the right table has repeated keys.
18

Explain the d, p, q and r functions for probability distributions in R.

Save to ▾
Show answer
Each distribution has four functions: d for density, p for cumulative probability, q for quantiles and r for random draws. dnorm(0) # density at 0 pnorm(1.96) # P(Z <= 1.96) = 0.975 qnorm(0.975) # 1.96 rnorm(5, mean = 50, sd = 10) dbinom(3, size = 10, prob = 0.2) # exactly 3 successes ppois(2, lambda = 4) # P(X <= 2) qt(0.975, df = 20) # t critical value
19

Your function gives correct results when you test it but wrong results in the main script. What could be the cause?

Save to ▾
Show answer
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.
20

Your model has R-squared 0.92 on training data but 0.55 on test data. What do you do?

Save to ▾
Show answer
The gap indicates overfitting or data leakage. I first check for leakage, such as a feature computed using future information or the target itself, or duplicate records across train and test. Then I simplify the model, add regularization, reduce features or tune hyperparameters with cross-validation instead of a single split. If data is time-based, I make sure the split is chronological. I compare against a simple baseline to judge real improvement.
21

What is the structure of an R package?

Save to ▾
Show answer
A minimal package has a DESCRIPTION file with metadata and dependencies, a NAMESPACE file listing exported functions, an R/ folder with function code, a man/ folder for documentation and usually tests/ for testthat tests. The usethis and devtools packages create and manage this: usethis::create_package(), use_r(), use_test(), and devtools::document(), test() and check(). Documentation is written as roxygen2 comments above each function. Internal packages are ideal for sharing team utilities.
22

Show different ways to index vectors and data frames in R.

Save to ▾
Show answer
R indexes from 1 and supports positive, negative, logical and name-based indexing. x <- c(a = 10, b = 20, c = 30, d = 40) x[2] # 20 x[-1] # drop first x[c('a', 'd')] # by name x[x > 15] # logical filter which(x > 15) # positions df[df$age > 30, c('name', 'city')] df[order(-df$salary), ][1:5, ] # top 5 salaries
23

What is the difference between Quarto and R Markdown?

Save to ▾
Show answer
Quarto is the next-generation publishing system from Posit. It works with R, Python, Julia and Observable, and is a standalone command-line tool rather than an R package. It uses the same knitr engine for R and similar syntax, with chunk options written as #| comments. Quarto adds consistent support for websites, books, presentations and dashboards. R Markdown is still supported, but new projects increasingly use Quarto, especially in mixed R and Python teams.
24

How do you test whether two categorical variables are related in R?

Save to ▾
Show answer
Use a chi-square test of independence on a contingency table; use Fisher's exact test when expected counts are small. tab <- table(survey$city_tier, survey$preferred_plan) tab res <- chisq.test(tab) res$p.value res$expected # check expected counts >= 5 fisher.test(tab) # for small samples # effect size library(rcompanion) cramerV(tab)
25

Salary data has a few extreme values that distort averages. How do you handle them?

Save to ▾
Show answer
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.
26

You need month-over-month sales growth for each product. How do you calculate it in R?

Save to ▾
Show answer
I first aggregate to one row per product and month with group_by() and summarise(). Then I group by product, arrange by month and use mutate(growth = (sales - lag(sales)) / lag(sales) * 100). The first month for each product becomes NA, which is correct. I make sure months with zero sales exist using tidyr::complete(), otherwise lag() compares non-consecutive months. Finally I round and format growth for reporting.
27

What is data.table and when would you use it?

Save to ▾
Show answer
data.table is a high-performance package for large tabular data. Its syntax DT[i, j, by] means filter rows with i, compute j, grouped by by. It updates columns by reference with :=, avoiding copies, and offers fast keyed joins and fread()/fwrite() for file IO. For example, DT[city == 'Delhi', .(avg_sal = mean(salary)), by = dept] filters and aggregates in one step. It is popular for datasets with tens of millions of rows.
28

How do you add labels and annotations to a ggplot chart?

Save to ▾
Show answer
Use geom_text() or geom_label() for data labels and annotate() for one-off notes. ggplot(city_sales, aes(reorder(city, sales), sales)) + geom_col(fill = 'steelblue') + geom_text(aes(label = scales::comma(sales)), hjust = -0.1, size = 3.5) + annotate('text', x = 2, y = 9e5, label = 'Festive season push') + coord_flip() + expand_limits(y = 1.1e6) + labs(x = NULL, y = 'Sales (INR)')
29

How do you perform a t-test in R?

Save to ▾
Show answer
t.test() handles one-sample, two-sample and paired tests. Welch's test, which does not assume equal variances, is the default. # two independent groups t.test(score ~ batch, data = students) # paired: before vs after training t.test(after, before, paired = TRUE) # one-sample against a target of 30 minutes t.test(delivery_time, mu = 30, alternative = 'greater') res <- t.test(score ~ batch, data = students) res$p.value; res$conf.int
30

A loop that adds rows to a data frame with rbind() takes very long. How do you speed it up?

Save to ▾
Show answer
Calling rbind() inside a loop copies the growing data frame each iteration, so time grows roughly quadratically. I instead store each piece in a preallocated list, results[[i]] <- piece, and combine once at the end with dplyr::bind_rows() or data.table::rbindlist(), which is the fastest. Better still is to vectorize the computation or use purrr::map() followed by list_rbind(). On 10,000 iterations this can cut runtime from minutes to seconds.
31

The same 40 lines of cleaning code are copied into ten scripts. What would you do?

Save to ▾
Show answer
I would extract the logic into well-named functions with arguments for the parts that differ, then move them into one shared file loaded with source() or, better, a small internal R package. A package gives documentation with roxygen2, tests with testthat and versioning, so every script uses the same tested logic. Fixing a bug then happens in one place. I would migrate the scripts gradually and compare outputs to confirm nothing changed.
32

How do you generate the same report for each of 20 regions automatically?

Save to ▾
Show answer
Use a parameterized Quarto or R Markdown report and render it in a loop. # report.qmd header: # params: # region: 'North' # then use params$region in the code regions <- unique(sales$region) purrr::walk(regions, function(r) { quarto::quarto_render( 'report.qmd', execute_params = list(region = r), output_file = paste0('report_', r, '.html') ) })
33

Write code using map2() and pmap() to iterate over multiple inputs.

Save to ▾
Show answer
map2() pairs two vectors and pmap() takes a list or data frame of arguments. library(purrr) prices <- c(100, 250, 400) qty <- c(2, 1, 5) map2_dbl(prices, qty, ~ .x * .y) # 200 250 2000 params <- tibble::tibble( region = c('North', 'South'), year = c(2025, 2026) ) pmap(params, function(region, year) { build_report(region = region, year = year) }) walk2(plots, paths, ~ ggsave(.y, .x))
34

What is a pure function and why prefer them in data pipelines?

Save to ▾
Show answer
A pure function always returns the same output for the same inputs and has no side effects, such as modifying global variables, writing files or printing. Pure functions are easy to test, reuse, cache and run in parallel, because they do not depend on hidden state. In data pipelines, I keep transformations pure, taking a data frame and returning a new one, and isolate side effects such as reading and writing in a few clearly named functions.
35

A numeric column in your data has been read as character. How do you fix it?

Save to ▾
Show answer
I first find the offending values, for example with unique(df$amount[is.na(as.numeric(df$amount))]), which reveals entries like '1,200', 'NA ' or '-'. Then I clean them, removing commas with gsub(',', '', x) and spaces with trimws(), and convert with as.numeric(). With readr I can also pass na = c('', '-', 'NA') and col_types so the column is parsed correctly at load time instead of being fixed later.
36

What is the purrr map family?

Save to ▾
Show answer
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.
37

What do geom_smooth() and stat_summary() do?

Save to ▾
Show answer
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.
38

An R script must run every morning at 7 AM and email a report. How do you schedule it?

Save to ▾
Show answer
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.
39

What is lazy evaluation in R?

Save to ▾
Show answer
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.
40

What does the ... (dots) argument do in an R function?

Save to ▾
Show answer
The dots let a function accept any number of additional arguments and pass them on to another function. For example, my_plot <- function(df, ...) ggplot(df, aes(x, y)) + geom_point(...) lets callers pass colour or size without declaring them. You can capture dots with list(...) or count them with ...length(). The downside is that misspelled argument names are silently ignored, so document them and validate with rlang::check_dots_used() in packages.

Frequently asked questions

What happens in the R Programming technical round round?

The technical round is the core of a R Programming interview: concept questions with follow-ups, a live hands-on task and a deep dive into one of your projects. Interviewers check depth, not just definitions.

How do I clear the technical round round?

Say your approach out loud before you start writing. After a correct answer, expect 'why?' and 'what if?' follow-ups; go one level deeper than the question. Pick one project you can explain end to end, including what you would do differently.

Which round comes next?

A typical order is online assessment, telephonic screening, one or two technical rounds, a managerial round and HR. Use the other round pages to practise each stage.