Data Analyst Interview Questions and Answers: 60 Questions from Easy to Hard
This guide is for freshers and working analysts in India preparing for data analyst interviews at product companies, IT services firms, banks, e-commerce and fintech startups. It has 60 questions with model answers in three levels: the basics every fresher is asked, the real-world problems analysts with one to four years face (wrong joins, messy data, conversion drops), and the senior questions on experiment design, metric traps and leading decisions with data. Every SQL query and every number in the answers was run or calculated. Read the question, answer it aloud in your own words, then compare, and take the mock test to practise under time.
- 60 questions, 3 levels
- Updated
- CoreHRx Jobs
- 1,646 open Data Analysis jobs
What data analyst interviews cover
- SQL in almost every round: joins, GROUP BY and HAVING, window functions, CTEs and removing duplicates, often as a live query on a sample table.
- Excel and a BI tool: lookups, SUMIFS and COUNTIFS, pivot tables, cleaning text and dates, and how you would build a simple dashboard.
- Statistics for business: mean and median, spread, distributions, percentiles, correlation versus causation, and the basics of A/B tests and significance.
- Metrics and product sense: defining KPIs, reading a funnel, cohort and retention analysis, and explaining why a number moved.
- Case and guesstimate questions, plus a round on how you present findings to a manager who has five minutes and no time for jargon.
How to prepare (freshers and experienced)
- Freshers: practise SQL every day on a small dataset you build yourself (orders, customers, events) until joins, GROUP BY and ROW_NUMBER feel routine, then add Excel lookups and pivots.
- Build one end-to-end project: a raw CSV, the cleaning steps, five clear questions, the queries, one chart per question and a one-page summary with a recommendation. Interviewers ask about it in depth.
- Experienced analysts: prepare two or three stories from your work as problem, data, analysis, decision and result, with the numbers you are allowed to share and what you would do differently.
- Revise statistics through examples, not formulas: why a median beats a mean for delivery times, what a p-value does and does not say, and how large an A/B test must be.
- For case rounds, practise thinking aloud with a structure: clarify the goal, break the metric into parts, list hypotheses, say which data you would check first, then recommend.
Common mistakes and interview-day tips
- Writing a query before asking what one row of each table represents; a join that duplicates rows quietly inflates every sum after it.
- Reporting the average of skewed data, or an average of ratios, without saying so. Always state the definition behind any number you give.
- Claiming that one thing caused another from a correlation or a before-and-after comparison with no control group.
- Presenting every chart you made. Lead with the answer and the decision it supports, then show only the evidence that backs it.
- In a live SQL round, say your assumptions out loud, test the logic on two or three rows in your head, and check row counts before and after each join.
Data Analysis interview questions: Easy questions (freshers)
20 questions1. What does a data analyst do, and what does a typical day look like?
A data analyst turns raw data into answers that help someone decide. A typical day mixes three kinds of work: ad hoc questions from business teams (why did orders drop in Hyderabad last week?), keeping recurring reports and dashboards correct, and deeper analyses such as a funnel or cohort study. Most of the time goes into writing SQL and cleaning and checking data, and only then into charts and a short written summary. Good analysts also spend time clarifying what the real question is before touching any data.
2. How is a data analyst different from a data scientist and a data engineer?
A data engineer builds and runs the pipelines that move data into the warehouse, so the tables exist, are fresh and can be trusted. A data analyst uses those tables to answer business questions with SQL, spreadsheets, BI dashboards and basic statistics, and explains what the numbers mean. A data scientist builds predictive models, such as demand forecasts or churn scores, and runs more advanced experiments. In small companies one person often does all three, so read the job description, not just the title.
3. What is the difference between WHERE and HAVING? Show it with a query.
WHERE filters individual rows before they are grouped; HAVING filters groups after the aggregates are calculated. In this query WHERE first drops orders below Rs 500, GROUP BY then counts what is left in each city, and HAVING keeps only the cities with more than two such orders. On the sample data Kochi has 4 qualifying orders and Pune 3, while Jaipur has only 2 and is left out. A common mistake is putting an aggregate such as COUNT(*) in WHERE, which is an error, because WHERE runs before any group exists.
SELECT city, COUNT(*) AS big_orders
FROM orders
WHERE amount >= 500
GROUP BY city
HAVING COUNT(*) > 2
ORDER BY city;4. What is the difference between an INNER JOIN and a LEFT JOIN, and why can a customer count drop after a join?
An INNER JOIN keeps only rows that have a match in both tables. A LEFT JOIN keeps every row of the left table and fills the right side with NULL when there is no match. If you join customers to orders with an INNER JOIN, customers who never ordered disappear, so a customer count drops. With the LEFT JOIN below all five customers appear, and Meena and Divya show 0 orders. Note COUNT(o.order_id): COUNT(*) would count the NULL-filled row and wrongly show 1 order for each of them.
SELECT c.name, COUNT(o.order_id) AS orders
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name
ORDER BY c.id;5. What is the difference between COUNT(*), COUNT(column) and COUNT(DISTINCT column)?
COUNT(*) counts rows, COUNT(column) counts the rows where that column is not NULL, and COUNT(DISTINCT column) counts the different non-NULL values. On a sample of 8 orders where 5 used a coupon and 4 different customers ordered, this query returns 8, 5 and 4. Analysts mix these up often: the number of orders is not the number of customers, and a coupon usage rate is 5 out of 8 orders, not 5 out of 4 customers. Always say which one a dashboard tile shows.
SELECT COUNT(*) AS orders,
COUNT(coupon_code) AS with_coupon,
COUNT(DISTINCT customer_id) AS customers
FROM orders;6. How do you find duplicate records in a table?
Group by the column or columns that should be unique and keep the groups with more than one row. Clean the key first: here one lead was typed as Ravi@example.com with a trailing space and another as ravi@example.com, so LOWER and TRIM make them match. On the sample both asha@example.com and ravi@example.com appear twice; without the cleaning only Asha would be caught. Agree with the business what a duplicate means before you count: the same email, the same phone, or the same person on the same day.
SELECT LOWER(TRIM(email)) AS clean_email, COUNT(*) AS copies
FROM leads
GROUP BY LOWER(TRIM(email))
HAVING COUNT(*) > 1
ORDER BY clean_email;7. What is the difference between VLOOKUP and INDEX/MATCH?
VLOOKUP searches the first column of a range and returns a value from a column to its right, given by number: here column 3, the city of employee 1003, which is Kochi. INDEX/MATCH splits the job: MATCH finds the position and INDEX returns the value from any column, even one to the left, so it can return Ravi for the city Jaipur. INDEX/MATCH also survives an inserted column, while a hard-coded 3 silently points at the wrong one. Always pass FALSE or 0 for an exact match, because VLOOKUP defaults to an approximate match.
A B C
1 Emp ID Name City
2 1001 Asha Pune
3 1002 Ravi Jaipur
4 1003 Meena Kochi
5 1004 Imran Indore
=VLOOKUP(1003, A2:C5, 3, FALSE) -> Kochi
=INDEX(B2:B5, MATCH("Jaipur", C2:C5, 0)) -> Ravi8. How do SUMIFS and COUNTIFS work?
SUMIFS adds the values in one range for the rows that meet every condition you give, and COUNTIFS counts those rows. In the example SUMIFS returns 3700: the West rows with an amount of at least 1000 (1200, 1500 and 1000). COUNTIFS returns 3, the West rows for Tea. Conditions are combined with AND; for OR, add two SUMIFS together. Watch the argument order: SUMIFS takes the sum range first, while the older SUMIF takes it last, which causes wrong totals when people switch between the two.
A B C
1 Region Product Amount
2 West Tea 1200
3 East Coffee 800
4 West Coffee 1500
5 West Tea 600
6 North Tea 2000
7 West Tea 1000
=SUMIFS(C2:C7, A2:A7, "West", C2:C7, ">=1000") -> 3700
=COUNTIFS(A2:A7, "West", B2:B7, "Tea") -> 39. What is a pivot table, and when would you use one?
A pivot table summarises a flat table by grouping and aggregating without writing formulas: for example, cities as rows, months as columns and the sum of order amount as values. You can switch the value to a count, an average or a percentage of the total, add filters or slicers, and double-click any number to see the rows behind it. Use it for quick exploration and recurring summaries. Two cautions: refresh it after the source data changes, because it does not update by itself, and keep the source as a clean table with one header row and no merged or blank cells.
10. When should you report the median instead of the mean?
The mean adds all the values and divides by the count; the median is the middle value after sorting. The mean is pulled by extreme values and the median is not. For seven delivery times of 22, 25, 24, 28, 26, 23 and 95 minutes, the mean is about 34.7 minutes but the median is 25, because one stuck order drags the mean up. For skewed data such as delivery times, order values or session lengths, report the median, perhaps with the 90th percentile. Use the mean when you need totals, since revenue = orders x average order value.
11. What do variance and standard deviation tell you, and which version should you use?
Variance is the average squared distance from the mean, and standard deviation is its square root, back in the original units. Two stores can both average 50 orders a day with very different spread: 50, 52, 48, 51 and 49 have a sample standard deviation of about 1.6, while 30, 70, 45, 65 and 40 have about 17.0. Use the sample version (divide by n - 1; STDEV.S in Excel, statistics.stdev in Python) when the data is a sample of a larger process, and the population version (divide by n; STDEV.P, statistics.pstdev) when you have every value.
12. What is the difference between correlation and causation?
Correlation means two variables move together; causation means changing one actually changes the other. Suppose a Mumbai dataset shows umbrella sales and food delivery orders rising together in the monsoon: umbrellas do not cause orders, rain drives both. Before claiming a cause, look for a third factor, check which came first, and prefer an experiment. Also remember that the correlation coefficient r measures only a straight-line relationship: for x from -2 to 2 and y = x squared, y depends completely on x, yet r is exactly 0.
13. You receive a new dataset. What do you check before analysing it?
First understand the grain: what one row represents, such as one order or one order line, and which column is the unique key. Then check the row count and date range, the data types, missing values per column, duplicates on the key, and the range of each number (negative amounts, an age of 200). Look at the distinct values of category columns for variants such as Bengaluru and Bangalore. Finally, reconcile one total with a trusted source, such as last month's revenue in the finance report, before you trust anything you calculate.
14. How do you handle missing values?
First find out why the values are missing, because the reason decides the fix. If a field was optional (no coupon used), the blank is information and may mean zero or none. If it is a tracking or entry error, you can drop the rows when they are few and random, fill them with the median or a group median, or add a flag column so the gap stays visible. Never fill silently: report how many rows were affected. Also know your tools: AVERAGE in Excel and AVG in SQL skip blanks and NULLs, so the denominator changes without warning.
15. How do you choose the right chart?
Start from the question, not the chart type. To show change over time, use a line chart. To compare categories, use a bar chart sorted by value, with horizontal bars when the labels are long. To show the distribution of one number, such as delivery time, use a histogram or a box plot. To show the relationship between two numbers, use a scatter plot. For parts of a whole, a stacked bar, or a pie with only two to four slices, works; beyond that, bars are easier to compare. Use a plain table when people need exact values.
16. What is the difference between a histogram and a bar chart?
A bar chart compares separate categories, such as orders by city; the bars have gaps and can be sorted in any order. A histogram shows the distribution of one continuous number, such as order value, by splitting it into ranges (bins) like Rs 0-200 and Rs 200-400; the bars touch and must stay in numeric order. The bin width changes the story: very wide bins hide a second peak and very narrow ones look noisy, so try a few widths. Use a histogram to spot skew, outliers and odd spikes, such as many orders at exactly Rs 499.
17. What is a KPI, and how is it different from a metric?
A metric is any number you track, such as app opens or average delivery time. A KPI is one of the few metrics tied directly to a business goal, with a target and an owner: for example, monthly repeat customers for a goal of improving loyalty. A good KPI is precisely defined (which orders, which dates, net or gross), measurable from reliable data, moves when the team does good work, and is hard to game. Pair each KPI with a guardrail metric, so that growth in orders is not bought with rising cancellations.
18. What is a conversion funnel, and how do you read one?
A funnel is the sequence of steps users take towards a goal, and step conversion is the share of users at one step who reach the next. Here 60% of app openers view a product, 25% of viewers add to cart, 60% of carts reach checkout and 75% of checkouts pay, so overall conversion is 1,350 out of 20,000, or 6.75%. The biggest leak is view to cart, so dig there first: prices, delivery charges shown late, items out of stock. Count unique users per step within a time window, not raw events.
Step Users
App open 20,000
Product view 12,000
Add to cart 3,000
Checkout 1,800
Payment done 1,35019. What is an A/B test, and why must users be assigned at random?
An A/B test splits users into a control group that sees the current version and a treatment group that sees the change, then compares a metric chosen in advance, such as conversion rate. Random assignment makes the two groups alike in everything else, such as city, device and loyalty, so a difference in the metric can be credited to the change. If users opt in, or only Android users get the new version, the groups differ and the gap may come from the people, not the change. Fix the metric, sample size and duration before you start.
20. Which Python basics should a data analyst know? Total the sales by city without any library.
Know lists, dictionaries, loops, functions, string methods and reading files, because pandas builds on the same ideas. This snippet groups a list of orders by city in a dictionary, using get to start each city at 0, then prints the cities from the highest total down: Indore 1500, Pune 1250 and Surat 650. In pandas the same job is a groupby on city with a sum of amount. Fresher interviews often ask for exactly this kind of loop, to check that you understand grouping and not just a library call.
orders = [
{"city": "Pune", "amount": 450},
{"city": "Indore", "amount": 1200},
{"city": "Pune", "amount": 800},
{"city": "Indore", "amount": 300},
{"city": "Surat", "amount": 650},
]
totals = {}
for o in orders:
totals[o["city"]] = totals.get(o["city"], 0) + o["amount"]
for city, total in sorted(totals.items(), key=lambda kv: -kv[1]):
print(city, total)Ready to check yourself? Take the free Data Analysis mock test
Data Analysis interview questions: Medium questions (1-3 years)
20 questions21. When do you use a window function instead of GROUP BY?
GROUP BY collapses rows into one row per group, so the detail is lost. A window function calculates over a group of rows but keeps every row, so you can show each sales rep next to the region total and their share of it: Kabir has 40.0% of North and Lakshmi 75.0% of South. Use GROUP BY for summary tables and window functions for rankings, running totals, shares and comparisons with the previous row. Multiply by 100.0 before dividing, as here; dividing two integers truncates in PostgreSQL.
SELECT rep, region, sales,
SUM(sales) OVER (PARTITION BY region) AS region_total,
ROUND(100.0 * sales
/ SUM(sales) OVER (PARTITION BY region), 1) AS pct
FROM rep_sales
ORDER BY region, sales DESC;22. How do you calculate month-over-month revenue growth with a CTE?
A CTE (the WITH clause) names an intermediate result so the query reads top to bottom. First aggregate orders to one row per month, then use LAG to bring the previous month's revenue onto the same row and compute the change. On the sample, growth is 15.0% in August, -5.0% in September and 20.0% in October; July is NULL because it has no previous month. Two traps: a month with no orders has no row, so LAG silently compares with two months back unless you fill months from a calendar table, and a zero month needs NULLIF to avoid dividing by zero.
-- order_month is text such as '2026-08': in MySQL 8 derive it
-- with DATE_FORMAT(order_date, '%Y-%m'), in PostgreSQL with
-- TO_CHAR(order_date, 'YYYY-MM')
WITH monthly AS (
SELECT order_month, SUM(amount) AS revenue
FROM orders
GROUP BY order_month
)
SELECT order_month, revenue,
ROUND(100.0 * (revenue - LAG(revenue) OVER w)
/ LAG(revenue) OVER w, 1) AS mom_pct
FROM monthly
WINDOW w AS (ORDER BY order_month)
ORDER BY order_month;23. How do you remove duplicates and keep only the latest record for each customer?
Number the rows of each customer with ROW_NUMBER, newest first, and keep row 1. Unlike GROUP BY with MAX(updated_at), this returns the whole latest row, including the plan. On the sample, customer 1 has three updates and keeps Gold, customer 3 keeps Silver, and customer 2 has a single row. Add a tie-breaker such as id to the ORDER BY, as here: two updates with the same timestamp would otherwise make the pick unpredictable between runs. Save the result as a view or a clean table rather than deleting the raw history.
WITH ranked AS (
SELECT customer_id, plan, updated_at,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY updated_at DESC, id DESC) AS rn
FROM customer_updates
)
SELECT customer_id, plan, updated_at
FROM ranked
WHERE rn = 1
ORDER BY customer_id;24. Revenue doubled after you joined orders to order items. Why, and how do you fix it?
The join changed the grain. Orders has one row per order but order_items has one row per item, so each order amount is repeated once per item and SUM counts it again and again. On the sample, true revenue is Rs 2,300, but the joined SUM gives Rs 5,100, because order 1 has three items and order 3 has two. Fix it by aggregating the many side to one row per order first, in a CTE, and joining one row to one row: the query below returns 2,300 and 6 items. Check row counts before and after every join to catch this early.
-- Wrong: SUM(o.amount) after joining order_items repeats
-- each order once per item.
WITH item_counts AS (
SELECT order_id, COUNT(*) AS items
FROM order_items
GROUP BY order_id
)
SELECT SUM(o.amount) AS revenue, SUM(ic.items) AS items
FROM orders o
JOIN item_counts ic ON ic.order_id = o.order_id;25. Your LEFT JOIN report lost the customers with no October orders. What went wrong?
The filter on the right table was in the WHERE clause. For a customer with no October order, the LEFT JOIN fills the order columns with NULL, and a condition such as o.order_date >= '2026-10-01' is never true for NULL, so WHERE removes those rows and the LEFT JOIN behaves like an INNER JOIN. Move the condition into the ON clause, as below: the dates then decide which orders match, and every customer stays. On the sample the fixed query returns all four customers (Asha 2, Ravi 0, Meena 0, Imran 1); the WHERE version returned only Asha and Imran.
SELECT c.name, COUNT(o.order_id) AS oct_orders
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.id
AND o.order_date >= '2026-10-01'
AND o.order_date < '2026-11-01'
GROUP BY c.id, c.name
ORDER BY c.id;26. VLOOKUP returns #N/A although you can see the value in the lookup table. What do you check?
Usually the two values only look the same. Check in this order: an ID stored as a number on one sheet and as text on the other (an exact match treats them as different), extra spaces or non-breaking spaces from a web export, the lookup value not sitting in the first column of the range, and a range that shifted when the formula was copied down because it was not locked with $ signs. Fix the data, or clean inside the formula with TRIM. Add IFERROR only after you know why values are missing, or genuine gaps get hidden.
=VLOOKUP(TRIM(A2), Master!$A$2:$C$500, 3, FALSE)
=IFERROR(VLOOKUP(TRIM(A2), Master!$A$2:$C$500, 3, FALSE),
"Not found")27. How do you clean messy names and amounts stored as text in Excel?
Clean with formulas in helper columns, check them, then paste as values. TRIM removes spaces at both ends and turns runs of spaces inside the text into one, and PROPER capitalises each word, so the name becomes Ravi Kumar. Amounts exported as text, such as 1,250, are skipped by SUM; SUBSTITUTE strips the comma and VALUE turns the text into the number 1250. Text copied from web pages often contains non-breaking spaces (character 160), which TRIM does not remove, so replace them first. For a file that arrives every month, record the steps once in Power Query.
A B
1 Name Amount (text)
2 " rAVI kumar " "1,250"
=PROPER(TRIM(A2)) -> Ravi Kumar
=VALUE(SUBSTITUTE(B2, ",", "")) -> 1250
=TRIM(SUBSTITUTE(A2, CHAR(160), " ")) (web data)28. What are percentiles and the interquartile range, and how do you use them to find outliers?
A percentile is the value below which a given share of the data falls: the median is the 50th, and Q1 and Q3 are the 25th and 75th. The interquartile range, Q3 - Q1, is the spread of the middle half and ignores extremes. For these 12 delivery times, QUARTILE.INC (the same as Python statistics.quantiles with method inclusive) gives Q1 = 21.75 and Q3 = 27.5, so the IQR is 5.75 and the upper fence, Q3 + 1.5 x IQR, is about 36.1: the 58-minute order is an outlier. Methods differ, so state yours; QUARTILE.EXC gives Q3 = 28.5 here.
A1:A12 = 18, 20, 21, 22, 23, 24, 25, 26, 27, 29, 31, 58
=QUARTILE.INC(A1:A12, 1) -> 21.75
=QUARTILE.INC(A1:A12, 3) -> 27.5
=QUARTILE.EXC(A1:A12, 3) -> 28.529. What is a normal distribution, and why be careful applying it to business data?
A normal distribution is symmetric and bell-shaped, with the mean and median equal; about 68.3% of values lie within one standard deviation of the mean and about 95.4% within two. Many methods assume it. Raw business data is rarely normal: order values and delivery times are right-skewed, with a long tail of large values. For ten baskets from Rs 150 to Rs 4,200 the mean is Rs 790 against a median of Rs 250, and the mean minus two standard deviations is below zero, which is meaningless for an amount. Plot a histogram first; use medians, percentiles or a log scale.
30. How do you handle outliers in a dataset?
First decide whether the outlier is an error or a real event. An order of Rs 1,20,000 in a grocery app may be a typo for Rs 1,200, a test order by the QA team, or a genuine bulk order from a restaurant. Errors get fixed or removed by a written rule; real values usually stay. If they distort the question, report the median, analyse that segment separately (bulk buyers), or cap values at a high percentile such as the 99th and say so. Never delete outliers just because they spoil a chart, and always report how many rows a rule touched.
31. Your dashboard shows different revenue from the finance team's report. How do you find the cause?
Compare definitions before code. Common causes: gross versus net revenue (GST, discounts, refunds and cancellations), the date used (order date versus delivery or invoice date), the time zone (a UTC timestamp puts orders placed between midnight and 5:30 am IST on the previous day), test or internal orders, and late-arriving or duplicate rows. Narrow it down: pick one day and one city, compare totals, then compare the order IDs to find rows in one list but not the other. Agree one written definition and show it on the dashboard.
32. What makes a chart misleading, and how do you avoid it?
The usual culprits are bar charts whose axis does not start at zero, dual axes scaled to make two lines look related, 3D effects, pies with many slices, uneven gaps between dates, and cumulative totals that hide a falling month. For example, satisfaction scores of 96 and 98 drawn as bars starting at 95 make one bar three times taller, although the real gap is 2 points, about 2%. Start bar axes at zero; a line chart may zoom in if the axis is labelled. Show the comparison that matters, such as against target or last year, and label the units.
33. Which metrics would you track for a food delivery app?
Start with one North Star metric that reflects value for customers and the business, such as orders delivered per week. Break it into inputs that teams can move: active customers, order frequency, menu-to-order conversion, restaurant availability and average order value. Add guardrails that must not get worse: delivery time, cancellation rate, refunds and complaints, and rider earnings per hour. Track unit economics too, such as contribution margin per order. Segment by city and by new versus repeat users, because a national average hides most problems.
34. Conversion rate fell 10% this week. How would you investigate?
First check the data: was there an app release, a tracking change, or a day of missing events? Many drops are logging bugs. Then pin down the size: a fall from 3.0% to 2.7% is 10% relative, 0.3 points absolute; compare with the same week last year for seasonality. Next, split by platform, app version, city, traffic source and new versus returning users to see where the drop sits, and find the funnel step that changed. Finally look outside: a payment gateway outage, a competitor sale, a festival. Report the cause with evidence, not a list of guesses.
35. How do you calculate monthly churn and retention for a subscription app?
Monthly churn is the share of subscribers at the start of the month who cancelled during it; retention is 1 minus churn. With 2,000 subscribers on 1 October and 160 of them cancelling during the month, churn is 160 / 2,000 = 8% and retention 92%; the 300 new sign-ups are tracked separately. Do not divide by the month-end count of 2,140, which mixes in new users and understates churn at about 7.5%. Small monthly rates compound: 8% churn a month leaves only about 37% of a group after 12 months, so even one point less churn is worth a lot.
36. What is cohort analysis, and how do you read a retention table?
Cohort analysis groups users by when they started, here the signup month, and tracks what share is still active in each month after. Read along a row to see one cohort decay and down a column to compare cohorts at the same age. August improved month-1 retention to 45% from July's 42%, while September fell to 38%, so ask what changed, such as a sale that brought in deal hunters. July's loss slows from 11 points to 4, a sign of a loyal core. The overall active-user count mixes old and new users and hides all of this.
Signup Users M1 M2 M3
Jul-26 1,000 42% 31% 27%
Aug-26 1,200 45% 33% -
Sep-26 900 38% - -
(M1 = share still active one month after signup)37. What does a p-value mean? Is this A/B test result significant?
A p-value is the probability of seeing a difference at least this large if the change truly had no effect; it is not the probability that B is better. Here B converts at 10.8% against 10.0%, an 8% relative lift, but a two-proportion z-test gives z of about 1.85 and a two-sided p-value of about 0.064, above the usual 0.05, so chance cannot be ruled out. That does not prove B has no effect: the 95% interval for the gap runs from about -0.05 to +1.65 points, so the test may simply be too small. Decide on business impact, not the p-value alone.
Group Users Orders Conversion
A 10,000 1,000 10.0%
B 10,000 1,080 10.8%
Two-proportion z-test: z = 1.85, p = 0.064 (two-sided)38. Estimate the number of food delivery orders placed in a day in a city of 70 lakh people.
State a structure and your assumptions, then check the answer another way. At about 4 people per household, 70 lakh people make 17.5 lakh households. Assume 40% of them order food online at least monthly (7 lakh households), about 4 times a month: 28 lakh orders a month, roughly 93,000 a day. Check from the supply side: if about 4,000 restaurants are active and each gets 20 to 25 delivery orders a day, that is 80,000 to 1,00,000, which is close. Interviewers grade the structure, the stated assumptions and the sanity check, not the exact number.
39. Write a small Python script that cleans a CSV of orders: drop repeated rows, fix city names, convert amounts and skip blanks.
The script reads the CSV with the csv module, skips repeated order IDs, strips spaces and fixes the case of city names with title, removes the thousands comma before converting amounts to int, and counts rows with a blank amount instead of silently treating them as zero. It prints Pune 2050 and Mumbai 2100 with one skipped row. In pandas the same steps are drop_duplicates, str.strip and str.title, to_numeric and a groupby with sum. Whatever the tool, count what each cleaning rule removed and report it next to the result.
import csv, io
raw = """order_id,city,amount
101, pune ,"1,250"
102,Pune,800
103,MUMBAI,
104,Mumbai,"2,100"
101, pune ,"1,250"
"""
seen, totals, skipped = set(), {}, 0
for row in csv.DictReader(io.StringIO(raw)):
if row["order_id"] in seen:
continue
seen.add(row["order_id"])
amount = row["amount"].replace(",", "").strip()
if not amount:
skipped += 1
continue
city = row["city"].strip().title()
totals[city] = totals.get(city, 0) + int(amount)
print(totals, "skipped:", skipped)40. How do you present an analysis to a manager who has five minutes?
Lead with the answer and the decision, then the evidence. For example: repeat orders fell 6% in October, almost all among users whose last order arrived late; I recommend a Rs 50 credit after any delivery over 45 minutes, tested first in two cities. Then show one or two charts that prove the point, state the size of the effect in rupees or customers, and name the main caveat, such as only one month of data. Keep the method and the extra cuts in an appendix. End with a clear ask: approve the test, or tell me what else you need to decide.
Ready to check yourself? Take the free Data Analysis mock test
Data Analysis interview questions: Hard questions (experienced)
20 questions41. How do you calculate a running total and a 7-day moving average in SQL, and what can go wrong?
Use window functions with an explicit frame: SUM over all rows up to the current one gives the running total, and AVG over the current row and the 6 before it gives the 7-day average. On the 10-day sample the running total reaches 1,220 and the average is 120.0 on day 7 and 130.0 on day 10. Traps: ROWS counts rows, not days, so a day with no orders silently stretches the window; fill missing days with zero from a calendar table first. The first six rows average fewer than seven days, so flag them. Without a frame, ORDER BY defaults to RANGE, which lumps tied dates together.
SELECT order_date, revenue,
SUM(revenue) OVER (ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW) AS running_total,
ROUND(AVG(revenue) OVER (ORDER BY order_date
ROWS BETWEEN 6 PRECEDING
AND CURRENT ROW), 1) AS avg_7d
FROM daily_revenue
ORDER BY order_date;42. Write a funnel query that counts users who viewed, then added to cart, then paid, in that order.
Turn each user's events into one row holding the first time of each step, using MIN with CASE, then count the users whose steps happened in order. On the sample of six users, 6 viewed, 3 added to cart after viewing and 2 paid after that. User 4 added to cart before viewing (say, from a shared cart link) and user 5 paid with no cart event (a buy-now button), so neither counts at those steps; agree with the product team whether such paths should. Real funnels also need a time window, such as payment within 24 hours of the view, and first-event logic misses users who come back later.
WITH steps AS (
SELECT user_id,
MIN(CASE WHEN ev = 'view' THEN ts END) AS t_view,
MIN(CASE WHEN ev = 'cart' THEN ts END) AS t_cart,
MIN(CASE WHEN ev = 'pay' THEN ts END) AS t_pay
FROM events
GROUP BY user_id
)
SELECT COUNT(t_view) AS viewed,
COUNT(CASE WHEN t_cart > t_view THEN 1 END) AS carted,
COUNT(CASE WHEN t_pay > t_cart
AND t_cart > t_view THEN 1 END) AS paid
FROM steps;43. How do you compute the median delivery time per city in SQL that runs on both MySQL 8 and PostgreSQL?
PostgreSQL has PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY minutes), but MySQL 8 has no median function, so a portable way is to rank the rows in each city and average the middle one or two. ROW_NUMBER orders the times, COUNT over the same partition gives n, and the condition 2 x rn between n and n + 2 picks the middle row when n is odd and the two middle rows when n is even, with no division that rounds differently across databases. On the sample, Lucknow (5 values) gives 31 and Pune (4 values) gives 27.5. Exclude NULLs first, or they shift the ranks.
WITH ranked AS (
SELECT city, minutes,
ROW_NUMBER() OVER (PARTITION BY city
ORDER BY minutes) AS rn,
COUNT(*) OVER (PARTITION BY city) AS n
FROM deliveries
WHERE minutes IS NOT NULL
)
SELECT city, AVG(minutes) AS median_minutes
FROM ranked
WHERE 2 * rn BETWEEN n AND n + 2
GROUP BY city
ORDER BY city;44. Your query on a 500-million-row orders table takes 20 minutes. How do you make it faster as an analyst?
Read less data. Filter the partition or indexed column with a plain range, as below, instead of wrapping it in a function such as DATE(created_at), which usually stops the database from using the index or pruning partitions. Select only the columns you need, which matters a lot in columnar warehouses. Aggregate before you join, so you join thousands of rows rather than millions. For questions asked every week, build a daily summary table once and query that. Read the plan with EXPLAIN, and try the logic on one day of data before running the full period.
-- Slow: WHERE DATE(created_at) = '2026-11-01'
-- Fast: a range on the raw column can use an index
SELECT city, COUNT(*) AS orders
FROM orders
WHERE created_at >= '2026-11-01'
AND created_at < '2026-11-02'
GROUP BY city
ORDER BY city;45. How do you look up a rate from a slab table, such as commission bands, in Excel, and what goes wrong?
Use an approximate match. VLOOKUP with TRUE, or MATCH with match type 1, finds the largest threshold that is less than or equal to the value, so sales of 1,45,000 fall in the 1,00,000 slab and get 3.5%. It needs the first column sorted in ascending order with the lowest band starting at 0: on unsorted data it can return wrong rates without any error, and a value below the first threshold gives #N/A. Lock the table with $ signs before copying down. In Microsoft 365, XLOOKUP with match mode -1 (exact match or next smaller item) does the same job more readably.
E F
1 From Rate
2 0 0%
3 50,000 2%
4 1,00,000 3.5%
5 2,00,000 5%
B2 = 1,45,000
=VLOOKUP(B2, $E$2:$F$5, 2, TRUE) -> 3.5%
=INDEX($F$2:$F$5, MATCH(B2, $E$2:$E$5, 1)) -> 3.5%46. The new checkout converts better on mobile and on desktop, but worse overall. How is that possible?
This is Simpson's paradox: a comparison reverses when groups with different mixes are combined. The new checkout beats the old one on mobile (20.0% against 18.0%) and on desktop (45.0% against 42.5%), yet loses overall (25.0% against 37.6%), because 80% of its users were on mobile, which converts less on any flow. The overall gap measures who got each flow, not how good it is. It shows up in staged rollouts and other observational data; compare within segments or reweight to the same mix. A randomised A/B test keeps the mix the same in both groups on average.
Flow Device Users Paid Rate
New Mobile 800 160 20.0%
New Desktop 200 90 45.0%
Old Mobile 200 36 18.0%
Old Desktop 800 340 42.5%
New overall: 250 / 1,000 = 25.0%
Old overall: 376 / 1,000 = 37.6%47. The 20 worst stores last quarter got a new training programme and improved 12% this quarter. Did the training work?
Not necessarily. Stores picked for being the worst in one quarter were partly unlucky, and bad luck tends not to repeat, so they would probably have improved anyway: this is regression to the mean. Compare them with similar low performers that did not get the training, or better, train a random half of the worst stores and compare the halves. Watch two related traps: survivorship bias (studying only stores that are still open hides the ones that closed) and selection bias (stores whose managers volunteered may differ from the rest). Always ask: compared with what?
48. How would you set up automated data quality checks for a daily orders table?
Run a small set of checks after every load and alert when one fails, before anyone opens the dashboard. Cover freshness (is the latest row from the last few hours?), volume (is today's row count within, say, 30% of the same weekday's average?), uniqueness of the key, null and range rules (no blank or negative amounts), valid category values, and referential integrity (every order has a known customer). Reconcile a daily total with the source system. The query below runs several checks in one pass. Store the results over time, and flag the dashboard when a critical check fails.
SELECT COUNT(*) AS total_rows,
COUNT(*) - COUNT(DISTINCT order_id) AS duplicate_ids,
SUM(CASE WHEN amount IS NULL THEN 1 ELSE 0 END) AS null_amt,
SUM(CASE WHEN amount < 0 THEN 1 ELSE 0 END) AS negative_amt,
MAX(created_at) AS latest_row
FROM orders;49. How do you design a dashboard for the leadership team?
Start from the decisions it supports and the five or six questions leaders ask every week, not from the data that happens to exist. Put the headline KPIs at the top with a comparison against target and the previous period, then trends, then breakdowns by the dimensions they act on, such as city or category. Keep one definition per metric, written on the page, and show when the data was last refreshed. Remove vanity numbers that never change a decision. Make it readable on a phone, and after a month check usage and cut what nobody opens.
50. When is an average of ratios misleading? Compare it with a ratio of totals.
Averaging ratios gives every group equal weight, while dividing totals weights each group by its volume. Here Pune and Kochi convert 50% each and Delhi 9%, so the average of the three city rates is 36.3%, while total orders over total visitors is 9.6%, the conversion of a typical visitor. They answer different questions: a typical city versus the business as a whole. Most business KPIs, such as conversion, average order value and delivery time, should be ratios of totals. Dashboards that average daily or city-level ratios quietly overweight small groups.
city visitors orders
Pune 100 50
Delhi 10,000 900
Kochi 60 30
SELECT ROUND(AVG(100.0 * orders / visitors), 1) AS avg_of_rates,
ROUND(100.0 * SUM(orders) / SUM(visitors), 1) AS overall
FROM city_daily;
-- avg_of_rates = 36.3, overall = 9.651. How do you design a metric so that a team cannot game it?
Any single target gets gamed: when a measure becomes a target, people optimise the number rather than the outcome, which is Goodhart's law. A support team measured on tickets closed per hour starts closing tickets without solving them. Design against it: choose a metric close to customer value, define it precisely (which tickets, what counts as resolved), pair it with counter-metrics such as reopen rate and customer satisfaction, and review a sample of the underlying cases every month. Prefer outcome metrics over activity counts, and revisit the definition when behaviour shifts.
52. Monthly active users are growing, but the product head suspects retention is getting worse. How do you check?
MAU mixes new and old users, so strong acquisition can hide weak retention: in the example, MAU grows from 19,000 to 28,250 while month-1 retention falls from 40% to 35%. Build retention curves by signup cohort and compare cohorts at the same age. Define active as a meaningful action, such as an order, not an app open triggered by a notification. Check whether the curves flatten (a loyal core) or keep sliding towards zero. Compare like with like: festive-sale cohorts often retain worse, so split by acquisition channel before blaming the product.
Month New From last From two MAU
month's new months ago
Jan 10,000 - - 10,000
Feb 15,000 4,000 (40%) - 19,000
Mar 20,000 5,250 (35%) 3,000 (30%) 28,25053. How do you decide the sample size and duration of an A/B test?
Sample size depends on the baseline rate, the smallest lift worth detecting, the significance level and the power. To detect a rise in conversion from 10% to 11% with a two-sided 5% test and 80% power, the standard two-proportion formula below needs 14,751 users per group, about 29,500 in total. With 4,000 eligible users a day split equally, that is about 7.4 days, so run two full weeks to cover weekday and weekend behaviour. Halving the detectable lift roughly quadruples the sample, which is why small lifts on low-traffic pages cannot be tested reliably.
from math import ceil, sqrt
from statistics import NormalDist
def n_per_group(p1, p2, alpha=0.05, power=0.80):
z_a = NormalDist().inv_cdf(1 - alpha / 2)
z_b = NormalDist().inv_cdf(power)
p = (p1 + p2) / 2
top = (z_a * sqrt(2 * p * (1 - p))
+ z_b * sqrt(p1 * (1 - p1) + p2 * (1 - p2)))
return ceil(top ** 2 / (p2 - p1) ** 2)
print(n_per_group(0.10, 0.11)) # 1475154. Why is it risky to check an A/B test every day and stop as soon as p drops below 0.05, or to test many metrics at once?
Both inflate false positives. A 5% threshold means a 5% false-alarm risk for one planned look at one metric. With five independent metrics, the chance that at least one shows p below 0.05 by luck is 1 - 0.95^5, about 22.6%. Peeking is similar: in a simulation of 2,000 A/A tests with no real difference, checked daily for 14 days, about 23% crossed 0.05 at some point. Fix the primary metric and sample size in advance and look once, or use a sequential method built for peeking. For several metrics, use a correction such as Bonferroni (0.05 / 5 = 0.01 each).
55. Your 50/50 A/B test has 50,600 users in control and 49,400 in treatment. Is that a problem?
Yes. This is a sample ratio mismatch: with 1,00,000 users split at random, a gap of 1,200 is far too large for chance. A chi-square test against the expected 50,000 each gives a statistic of 14.4 and a p-value of about 0.00015. Until you find the cause, do not trust the result, because the missing users are rarely random: the treatment page may load slower so impatient users leave before being logged, a crash may hit only the new version, or bot filtering may differ. Check the split by day, platform and app version to see where the gap starts.
56. A delivery fee was launched in one city only, so you could not run an A/B test. How do you measure its effect?
Use a comparison city and difference-in-differences. Jaipur got the fee and fell by 400 orders a day; Indore, which did not, fell by 200 over the same weeks, perhaps because the festive season ended. The estimated effect is the difference between the two changes: about -200 orders a day, or 4% of Jaipur's baseline. The key assumption is parallel trends, so check that both cities moved together for several weeks before the launch, and look for anything else that hit only one city, such as a local holiday or a competitor's offer.
City Before After Change
Jaipur 5,000 4,600 -400 (fee launched)
Indore 4,000 3,800 -200 (no change)
Effect = -400 - (-200) = -200 orders a day57. Revenue fell 15% month over month. How do you find out why?
Break revenue into a product of drivers, revenue = visitors x conversion x average order value, and see which factor moved. Here visitors fell 4% and AOV rose 6.25%, but conversion fell from 3.0% to 2.5%, a 16.7% drop, so conversion is the main cause (0.96 x 0.833 x 1.0625 = 0.85). Then drill into conversion by device, city, new versus returning users and funnel step, and line it up with events such as a price change or payment failures. The higher AOV may be part of the same story if the lost orders were the small baskets.
Month Visitors Conversion AOV Revenue
Sep 5,00,000 3.0% Rs 800 Rs 1.20 crore
Oct 4,80,000 2.5% Rs 850 Rs 1.02 crore
Change -4.0% -16.7% +6.25% -15.0%58. A Bengaluru quick-commerce company wants to lower its free-delivery threshold from Rs 299 to Rs 199. How would you analyse it?
Frame it as a trade-off and estimate the break-even before testing. Suppose 1,00,000 orders a week, 30% of them between Rs 199 and Rs 298 and paying a Rs 25 fee: the company gives up Rs 7.5 lakh a week in fees. If each extra order earns Rs 40 of contribution, it needs 18,750 extra orders a week, an 18.75% lift, just to break even, before any shrinking of baskets. Then test it: randomise by user, or by area if riders are shared, with contribution margin per user as the main metric and order frequency, AOV and delivery time as guardrails.
59. A 3 GB CSV will not open in Excel. How do you analyse it in Python?
Excel stops at 1,048,576 rows, and loading the whole file into memory may fail too. Stream it instead: the csv module reads one row at a time, so memory stays flat however large the file is. The function below totals amount by city and counts bad rows (a missing column or a non-numeric amount) instead of crashing on the first one. In pandas, read_csv with chunksize and usecols does the same in blocks. For repeated work, load the file once into a database and use SQL. Check the bad-row count before you trust the totals.
import csv
from collections import defaultdict
def city_totals(path):
totals, bad = defaultdict(int), 0
with open(path, newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
try:
totals[row["city"]] += int(row["amount"])
except (KeyError, TypeError, ValueError):
bad += 1
return dict(totals), bad60. A senior manager asks you to find data that supports a decision already made, and your analysis does not. What do you do?
Share what the data shows early and privately, before any meeting. Restate the question as the decision it informs, show the evidence clearly, and separate what you are confident about from what is uncertain. Offer options rather than a flat no: a smaller pilot, a test in two cities, or the conditions under which the plan would work. Never cherry-pick dates or segments to make the numbers fit; your value as an analyst is that people trust your numbers. If the manager still goes ahead, write down the assumptions and the metric you will review after launch.
Practice questions for November 2026
New every month3 questions from the Data Analysis mock test, the way they appear in it. A new set comes on the 1st of every month. Pick your answer, then open it.
-
Asha appears twice in column B. What does the formula in E2 return?
A B C 1 Region Rep Sales 2 North Asha 1200 3 South Ravi 800 4 North Meera 1500 5 East Kiran 700 6 South Asha 900 E2: =VLOOKUP("Asha", B2:C6, 2, FALSE)- 900
- 1200
- 2100
- #N/A
Show the answer
Answer: 1200. An exact-match VLOOKUP stops at the first match from the top, so it returns the 1200 on row 2 and ignores row 6. To add up all of Asha's sales use SUMIFS(C2:C6, B2:B6, "Asha"), which gives 2100.
-
How does a snowflake schema differ from a star schema?
- It stores all the dimension attributes inside the fact table itself
- It has no fact table at all, only dimension tables linked together
- Its dimensions are normalised into linked sub-tables, such as product to category
- Its fact table is split into a separate physical table for every single measure it holds
Show the answer
Answer: Its dimensions are normalised into linked sub-tables, such as product to category. In a star schema each dimension is one wide table joined directly to the fact. A snowflake splits dimensions into normalised tables, saving a little space but adding joins that make queries slower and harder to write.
-
A food platform earns 20% commission on a Rs 400 order, pays Rs 50 for delivery, funds a Rs 20 discount and pays a 2% payment-gateway fee on the order value. What is its contribution per order?
- Rs 2
- Rs 22
- Rs 30
- Rs 10
Show the answer
Answer: Rs 2. Commission is 0.20 x 400 = Rs 80. Subtracting delivery (50), the discount (20) and the gateway fee (0.02 x 400 = 8) leaves Rs 2 per order. Small per-order costs decide whether growth makes or loses money.
Ready to apply?
There are 1,646 open jobs asking for Data Analysis on CoreHRx right now. Create a free profile once and apply with it.