Joins • GROUP BY • Window Functions • Indexes • Transactions • 2026

SQL Interview Questions

32 questions What each one tests, an answer frame, a spoken answer 36 min read

This page is for developers and testers facing a SQL round, from a first job to a senior backend role. Most SQL interviews start with joins and GROUP BY, move on to subqueries, CTEs and window functions, then test NULLs, duplicates and the difference between DELETE, TRUNCATE and DROP. Stronger rounds add indexes, query plans and transactions, plus a story about real data. Every query question comes with a query written in standard SQL, and where engines differ, the answer says so. Read what the interviewer is checking, practise the spoken answer out loud, then swap in your own stories.

Search all questions by round, difficulty and level, or save the ones you want to practise.

Joins 5 questions

Easy Technical round Fresher, Mid-level Practice question

1. Walk me through INNER, LEFT, RIGHT and FULL OUTER joins. Which rows does each one keep?

What the interviewer is really testing:
Whether you can predict exactly which rows come back, including the unmatched ones, rather than just reciting the names.
Answer frame:

Inner: only rows that match on both sides.

Left and right: every row from one side; the other side's columns are NULL where nothing matches.

Full outer: every row from both sides, matched where possible, with NULLs filling the gaps.

Cross: every row paired with every row, no join condition.

Sample spoken answer:

"An inner join keeps only the pairs that match on the join condition, so a customer with no orders just disappears. A left join keeps every row from the left table, and where there's no match on the right, the right-hand columns come back as NULL. A right join is the mirror image, and in practice I rewrite it as a left join by swapping the tables because it reads more easily. A full outer join keeps everything from both sides and fills NULLs wherever one side is missing, which is handy when I'm comparing two lists to see what's only in one. A cross join has no condition at all: every row meets every row, so three rows and four rows give twelve. One thing I always keep in mind is that if a key repeats on the other side, a join returns one row per match, so the row count can grow."

Code:
SELECT c.name, o.id AS order_id
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id;
-- customers with no orders appear once, with order_id NULL
Red flag to avoid:

Saying a left join returns exactly the rows of the left table, without realising that several matches on the right repeat those rows.

They may ask next:
  • If a customer has three orders, how many rows does that customer produce in a left join?
  • MySQL has no FULL OUTER JOIN keyword. How would you get the same result there?
Say it in 60 seconds
Easy Coding round Fresher, Mid-level Practice question

2. An employees table has id, name and manager_id. Write a query that shows each employee next to their manager's name.

What the interviewer is really testing:
Whether you can join a table to itself with clear aliases, and remember the people at the top who have no manager.
Answer frame:

Self join: the same table twice with two aliases, one for the employee and one for the manager.

Left join: keeps the top of the tree, whose manager_id is NULL.

Readable output: alias the columns so nobody confuses the two names.

Sample spoken answer:

"I'd join the employees table to itself. I give it two aliases, e for the employee and m for the manager, and join on e.manager_id equals m.id. The important choice is a left join rather than an inner join, because the person at the top has a NULL manager_id and an inner join would silently drop them. Then I select e.name as employee and m.name as manager so the output is clear, and I might wrap the manager name in COALESCE to show 'No manager' instead of NULL. The same pattern works for any table that points at itself, like categories with a parent category, or comments that reply to other comments."

Code:
SELECT e.name AS employee,
       COALESCE(m.name, 'No manager') AS manager
FROM employees e
LEFT JOIN employees m ON m.id = e.manager_id
ORDER BY e.name;
Red flag to avoid:

Using an inner join and losing the top-level employees without noticing.

They may ask next:
  • How would you list each manager together with how many direct reports they have?
  • How would you get every level above an employee, not just the direct manager?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level Practice question

3. You add a filter on the right-hand table to a LEFT JOIN, and suddenly the unmatched rows vanish. Why, and how do you fix it?

What the interviewer is really testing:
Whether you understand that a WHERE filter on the outer side quietly turns a left join back into an inner join.
Answer frame:

Cause: unmatched rows have NULL in the right-hand columns, and WHERE drops them because a test against NULL is never true.

Fix: move the condition into the ON clause, so it limits which rows match rather than which rows survive.

Check: compare row counts before and after the change.

Sample spoken answer:

"Say I left join customers to orders and add WHERE o.status = 'shipped'. For customers with no orders, every order column is NULL, and NULL equals 'shipped' isn't true, so WHERE throws those customers away. The query now behaves exactly like an inner join. The fix is to put that condition in the ON clause instead. In ON, it only decides which orders count as a match. Every customer still comes through, and the ones without a shipped order just get NULLs. The rule I keep in my head is that ON controls matching and WHERE filters the final result. For an inner join the two places give the same answer, but for an outer join they don't."

Code:
-- Keeps every customer; only shipped orders are joined
SELECT c.name, o.id AS order_id
FROM customers c
LEFT JOIN orders o
  ON o.customer_id = c.id
 AND o.status = 'shipped';
Red flag to avoid:

Blaming the data or adding DISTINCT, instead of seeing that WHERE turned the outer join into an inner one.

They may ask next:
  • When is it correct to test a right-hand column in WHERE after a left join?
  • If you put a condition on the left table's column inside ON, does it filter out left rows?
Say it in 60 seconds
Medium Coding round Fresher, Mid-level, Senior Practice question

4. Find customers who have never placed an order. Give me more than one way to write it, and tell me where NOT IN can go wrong.

What the interviewer is really testing:
Whether you know the anti-join patterns and the NULL trap that makes NOT IN return no rows at all.
Answer frame:

NOT EXISTS: a correlated check that no matching order exists; safe with NULLs.

LEFT JOIN with IS NULL: join, then keep rows where the order side is missing.

NOT IN trap: if the subquery returns even one NULL, the filter returns no rows.

Sample spoken answer:

"My default is NOT EXISTS: select customers where there isn't an order with that customer_id. It's clear and it handles NULLs correctly. Another way is a left join from customers to orders, keeping rows where the order's id is NULL. The one I'm careful with is NOT IN. If I write id NOT IN a subquery of order customer ids, and any order has a NULL customer_id, the result is empty. That's because NOT IN is a chain of not-equal checks, and a comparison with NULL is unknown, so no row ever passes. If I do use NOT IN, I add customer_id IS NOT NULL inside the subquery. Many optimizers turn NOT EXISTS and the left join version into a similar anti-join, but I'd check the plan rather than assume."

Code:
SELECT c.id, c.name
FROM customers c
WHERE NOT EXISTS (
  SELECT 1 FROM orders o WHERE o.customer_id = c.id
);

-- Same result
SELECT c.id, c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;
Red flag to avoid:

Using NOT IN against a nullable column and not knowing why the query suddenly returns zero rows.

They may ask next:
  • In the left join version, why test the order's primary key for NULL rather than any order column?
  • How would you change this to customers with no orders in the last 90 days?
Say it in 60 seconds
Medium Situational round Mid-level, Senior Practice question

5. A query's totals look too high, and a teammate's fix is to add DISTINCT. What do you check before accepting that?

What the interviewer is really testing:
Whether you recognise join fan-out, where joining two one-to-many tables multiplies rows, and fix the cause rather than masking it.
Answer frame:

Suspect the join: a one-to-many join repeats parent rows, and SUM counts them every time.

Prove it: count rows per key before and after each join.

Fix properly: aggregate each child table to one row per key first, then join; DISTINCT can drop real rows and doesn't fix a SUM.

Sample spoken answer:

"Totals that are too high usually mean a join is repeating rows. For example, if the query joins orders to both order_items and payments, an order with three items and two payments turns into six rows, and a SUM of the order's total counts it six times. DISTINCT might make a list of rows look right, but it doesn't fix the SUM, and SUM(DISTINCT) is worse, because two different orders with the same total would be counted once. So I'd check the row count per order before and after each join to find where it multiplies. The proper fix is to aggregate each child table down to one row per order in its own subquery or CTE, then join those results. I'd walk the teammate through the six-row example, because it's a mistake almost everyone makes once."

Red flag to avoid:

Accepting DISTINCT because the numbers now look plausible, without finding which join multiplies the rows.

They may ask next:
  • How would you write a test that catches this kind of fan-out next time?
  • When is DISTINCT actually the right tool?
Say it in 60 seconds

Query Basics 4 questions

Easy Technical round Fresher, Mid-level Practice question

6. What is the difference between WHERE and HAVING? Show me a query that needs both.

What the interviewer is really testing:
Whether you know that WHERE filters rows before grouping and HAVING filters groups after, and put each condition in the right place.
Answer frame:

WHERE: filters single rows before GROUP BY, so it can't use aggregates.

HAVING: filters whole groups after aggregation; conditions on COUNT or SUM go here.

Both: narrow the rows first with WHERE, then test the groups with HAVING.

Sample spoken answer:

"WHERE runs on individual rows before any grouping happens, so it can't see aggregates like COUNT or SUM. HAVING runs after GROUP BY and filters the groups themselves. A simple example: I want students who sat more than three exams since the start of 2025. The date condition goes in WHERE, because it's about single exam rows, and it also means fewer rows get grouped. Then I group by student and put COUNT(*) > 3 in HAVING, because that's a fact about the group, not about any one row. You can technically put a plain condition on a grouped column in HAVING, but I keep row conditions in WHERE because it's clearer and filters earlier."

Code:
SELECT student_id, COUNT(*) AS exams_taken
FROM exam_results
WHERE taken_on >= '2025-01-01'
GROUP BY student_id
HAVING COUNT(*) > 3;
Red flag to avoid:

Calling HAVING just another WHERE, or putting an aggregate in WHERE and not knowing why it errors.

They may ask next:
  • Why can you use a SELECT alias in ORDER BY but not in WHERE?
  • Can you use HAVING without GROUP BY? What would it mean?
Say it in 60 seconds
Easy Technical round Fresher, Mid-level Practice question

7. In what order does the database logically process the clauses of a SELECT, and how does that explain some common errors?

What the interviewer is really testing:
Whether you know the logical processing order well enough to explain why aliases, aggregates and window functions only work in certain clauses.
Answer frame:

Order: FROM and joins, WHERE, GROUP BY, HAVING, SELECT, DISTINCT, ORDER BY, then LIMIT or TOP.

Consequences: WHERE can't see SELECT aliases, aggregates or window functions; ORDER BY can see aliases.

Logical, not physical: the optimizer may run steps differently as long as the result is the same.

Sample spoken answer:

"The clauses are written in one order but processed logically in another. First FROM and the joins build the rows, then WHERE filters them, then GROUP BY forms groups and HAVING filters those groups. Only after that does SELECT work out the output columns, including window functions, then DISTINCT removes repeats, ORDER BY sorts, and LIMIT or TOP cuts the result. That explains a few classic errors. I can't use a SELECT alias in WHERE, because WHERE runs before SELECT, but I can use it in ORDER BY. I can't put COUNT in WHERE, because the groups don't exist yet. And a window function can't go in WHERE either, so I wrap it in a CTE and filter outside. This is the logical order, though. The optimizer is free to run things differently as long as the answer comes out the same."

Code:
SELECT class_id, AVG(score) AS avg_score
FROM results
WHERE taken_on >= '2025-01-01'  -- no aliases visible yet
GROUP BY class_id
HAVING AVG(score) > 60          -- repeat the expression, not the alias
ORDER BY avg_score DESC;        -- the alias is visible here
Red flag to avoid:

Believing the query runs top to bottom as written, and so being unable to explain why an alias fails in WHERE.

They may ask next:
  • Why does selecting a column that is neither grouped nor aggregated fail in most engines?
  • Where do window functions fit in this order, and what does that mean for filtering on them?
Say it in 60 seconds
Easy Technical round Fresher, Mid-level Practice question

8. What's the difference between UNION and UNION ALL, and which one do you use by default?

What the interviewer is really testing:
Whether you know UNION removes duplicates at a cost, and that UNION ALL is the right default when duplicates are impossible or wanted.
Answer frame:

UNION: stacks the results and removes duplicate rows, which needs a sort or hash step.

UNION ALL: stacks the results as they are; faster, and keeps every row.

Rules: same number of columns in the same order with compatible types; the names come from the first query.

Sample spoken answer:

"Both stack the results of two queries on top of each other. UNION then removes duplicate rows across the whole result, which means the database has to sort or hash everything to find them. UNION ALL skips that and just returns every row. My default is UNION ALL, because often the two sets can't overlap anyway, like a live table and an archive table, and then UNION is paying for work that does nothing. If duplicates might exist and I really need them gone, I use UNION on purpose. Either way, both queries need the same number of columns in the same order with compatible types, and the column names come from the first query. That's different from a join, which puts columns side by side instead of stacking rows."

Code:
SELECT id, email, 'active' AS source FROM users
UNION ALL
SELECT id, email, 'archived' AS source FROM archived_users;
Red flag to avoid:

Using UNION everywhere to be safe, without knowing it costs a de-duplication step.

They may ask next:
  • If you add ORDER BY, where does it go and what does it sort?
  • How are INTERSECT and EXCEPT different from these two?
Say it in 60 seconds
Medium Behavioral round Fresher, Mid-level Practice question

9. Tell me about a time you used SQL to track down a bug or prove what really happened, rather than to build a feature.

What the interviewer is really testing:
Whether you use SQL as an investigation tool, form a hypothesis from the data, and turn what you found into a check that stays.
Answer frame:

Symptom: what users or tests reported.

Queries: how you narrowed it down, step by step.

Outcome: the root cause, the fix, and the check you left behind.

Sample spoken answer:

"In my last role I was testing an order flow, and a few customers were getting two confirmation emails. The app logs weren't clear, so I went to the data. First I grouped the orders table by customer and cart id with HAVING COUNT(*) > 1, and found a small number of carts with two orders created within a second of each other. Then I joined to the payments table and saw both orders carried the same payment reference. That pointed to a double click on the pay button sending two requests. I shared the query and the rows with the developer. They added a unique constraint on the cart id and disabled the button after the first click, and I turned my duplicate query into a check that runs after every test cycle, so it would flag the problem if it ever came back."

Red flag to avoid:

Only ever reporting that something looks wrong on screen, without checking the data behind it.

They may ask next:
  • How did you make sure your queries didn't put load on the live database?
  • What would you have done without read access to production data?
Say it in 60 seconds

Subqueries & CTEs 3 questions

Medium Coding round Fresher, Mid-level Practice question

10. What is a correlated subquery? Write one that lists students who scored above their own class average.

What the interviewer is really testing:
Whether you understand a subquery that depends on the outer row, and whether you know a set-based alternative.
Answer frame:

Correlated: the inner query refers to a column of the outer row, so logically it runs once per outer row.

Query: compare each score with the average of the rows in the same class.

Alternative: a window AVG partitioned by class, or a join to a grouped subquery.

Sample spoken answer:

"A normal subquery runs once and hands back a result. A correlated one refers to the current outer row, so logically it's re-run for each row. Here, for each result row I compute the average score of the rows with the same class_id, and keep the row if its score is higher. It reads naturally, and many optimizers rewrite it into a join, but on a big table it can be slow if the engine really does run it row by row. The alternative I'd often use is a window function: AVG(score) OVER (PARTITION BY class_id) in a CTE, then filter on it in the outer query. That reads the table once, and I can show the average next to each score, which people usually want anyway."

Code:
SELECT r.student_id, r.class_id, r.score
FROM results r
WHERE r.score > (
  SELECT AVG(r2.score)
  FROM results r2
  WHERE r2.class_id = r.class_id
);
Red flag to avoid:

Not being able to say what makes the subquery correlated, or claiming it always runs once per row no matter what the optimizer does.

They may ask next:
  • Rewrite it with a window function. Why can't you put the window function straight into WHERE?
  • How would you check whether the engine really ran it once per row?
Say it in 60 seconds
Medium Technical round Mid-level, Senior Practice question

11. When do you use a CTE instead of a subquery? Does a CTE make a query faster?

What the interviewer is really testing:
Whether you pick CTEs for readability and reuse, and know their speed depends on the engine rather than assuming they're faster.
Answer frame:

Readability: a CTE names each step, so a long query reads from top to bottom.

Reuse and recursion: one CTE can be referenced more than once, and only a CTE can be recursive.

Speed: often the same plan as the subquery; some engines store the CTE result, which can help or hurt, so check the plan.

Sample spoken answer:

"I use a CTE when a query has steps. Instead of nesting a subquery inside a subquery, I write WITH filtered as, then ranked as, then a final select, and anyone reading it can follow the logic top to bottom. It also helps when I need the same derived result twice in one query, and it's the only way to write a recursive query. On speed, I don't assume a CTE is faster. In many engines the optimizer treats a simple CTE just like an inline subquery and produces the same plan. Some engines, or older versions of them, compute the CTE once and store the result, which can help when it's reused but can hurt because filters from outside may not get pushed into it. So I write it for clarity first and let the query plan settle the speed question."

Red flag to avoid:

Claiming a CTE is always faster or always cached, with no mention of checking the plan.

They may ask next:
  • Is a CTE the same thing as a temporary table? When would you pick a temp table instead?
  • Can a second, separate statement in the same session use a CTE you defined earlier?
Say it in 60 seconds
Hard Coding round Mid-level, Senior Practice question

12. Using an employees table with manager_id, write a query that returns everyone under a given manager, at every level, with their depth.

What the interviewer is really testing:
Whether you can write a recursive CTE with a correct anchor and recursive step, and know how to stop it looping on bad data.
Answer frame:

Anchor: the direct reports of the starting manager, at depth 1.

Recursive step: join employees to the rows found so far, adding one to depth.

Stop: it ends when a step finds no new rows; guard against cycles with a depth limit.

Sample spoken answer:

"This is a recursive CTE. The anchor part selects the direct reports of the manager I start from, say id 7, and marks them depth 1. Then UNION ALL with the recursive part, which joins employees to the CTE itself where manager_id equals the id of someone already found, and adds one to depth. The database keeps running that step until it finds no new rows. The risk is bad data: if two people end up as each other's managers, it loops forever. So I add a depth limit in the recursive part, or track the path and stop when an id repeats. The keyword differs a little by engine: PostgreSQL and MySQL want WITH RECURSIVE, while SQL Server just uses WITH."

Code:
WITH RECURSIVE reports AS (
  SELECT id, name, manager_id, 1 AS depth
  FROM employees
  WHERE manager_id = 7
  UNION ALL
  SELECT e.id, e.name, e.manager_id, r.depth + 1
  FROM employees e
  JOIN reports r ON e.manager_id = r.id
  WHERE r.depth < 20
)
SELECT id, name, depth
FROM reports
ORDER BY depth, name;
Red flag to avoid:

Writing the recursion without a clear anchor, or never thinking about what happens if the data contains a cycle.

They may ask next:
  • How would you walk the other way, from one employee up to the top?
  • Why UNION ALL here rather than UNION?
Say it in 60 seconds

Window Functions 5 questions

Easy Technical round Fresher, Mid-level Practice question

13. ROW_NUMBER, RANK and DENSE_RANK all number rows. What does each one do when two rows tie?

What the interviewer is really testing:
Whether you know exactly how ties are numbered, since picking the wrong function silently changes the result.
Answer frame:

ROW_NUMBER: always unique, 1, 2, 3, 4; ties are broken in no guaranteed order.

RANK: ties share a number and the next one skips: 1, 2, 2, 4.

DENSE_RANK: ties share a number with no gap: 1, 2, 2, 3.

Sample spoken answer:

"All three need an ORDER BY inside OVER, and optionally a PARTITION BY to restart the numbering per group. Say the scores are 95, 90, 90 and 85. ROW_NUMBER gives 1, 2, 3, 4. Every row gets a unique number, and which of the two 90s gets 2 isn't guaranteed unless I add a tiebreaker column to the ORDER BY. RANK gives 1, 2, 2, 4, like a sports table: two people tied for second, so nobody is third. DENSE_RANK gives 1, 2, 2, 3, with no gaps. I pick based on the question. For exactly one row per group, ROW_NUMBER. For the second highest distinct value, DENSE_RANK. For a leaderboard where a tie pushes the next person down, RANK."

Code:
SELECT student_id, score,
  ROW_NUMBER() OVER (ORDER BY score DESC) AS row_num,
  RANK()       OVER (ORDER BY score DESC) AS rnk,
  DENSE_RANK() OVER (ORDER BY score DESC) AS dense_rnk
FROM results;
Red flag to avoid:

Mixing up RANK and DENSE_RANK, or thinking ROW_NUMBER gives tied rows the same number.

They may ask next:
  • How would you make ROW_NUMBER give the same result every time the query runs?
  • What does adding PARTITION BY change in these results?
Say it in 60 seconds
Medium Coding round Fresher, Mid-level Practice question

14. Find the second highest score in a results table. What should happen if the top score is shared, or if there's no second score at all?

What the interviewer is really testing:
Whether you think about ties and empty results in this classic question, not just the happy path.
Answer frame:

Clarify: second highest distinct value, or the second row? Usually the distinct value.

Approach: the MAX below the MAX, or DENSE_RANK equal to 2.

Edge cases: say what comes back when there is no second value: a NULL or no rows.

Sample spoken answer:

"First I'd ask what second highest means. If the scores are 100, 100 and 90, most people mean 90, the second distinct value. The simplest query is the max of all scores below the overall max. That handles the tie at the top, and if every score is the same, MAX over no rows returns NULL, which is a clean way of saying there isn't one. The more general version uses DENSE_RANK ordered by score descending and keeps rank 2, which extends easily to the Nth highest. With that version, if there's no rank 2 you get no rows, so if the caller needs a single NULL I wrap it in a scalar subquery. I'd avoid LIMIT 1 OFFSET 1 on its own, because with a tie at the top it returns 100 again."

Code:
-- Simple, and returns NULL when there is no second score
SELECT MAX(score) AS second_highest
FROM results
WHERE score < (SELECT MAX(score) FROM results);

-- Generalises to the Nth highest
SELECT DISTINCT score
FROM (
  SELECT score, DENSE_RANK() OVER (ORDER BY score DESC) AS rnk
  FROM results
) ranked
WHERE rnk = 2;
Red flag to avoid:

Using LIMIT with OFFSET and never thinking about ties, or not saying what the query returns when there is no second score.

They may ask next:
  • How would you return the second highest score for each class?
  • If some scores are NULL, can either query give a different answer?
Say it in 60 seconds
Hard Coding round Mid-level, Senior Practice question

15. Return the top three scorers in each class. How do you handle a tie for third place?

What the interviewer is really testing:
Whether you can combine PARTITION BY with ranking, filter it one level up, and make a deliberate choice about ties.
Answer frame:

Rank per group: a window function partitioned by class, ordered by score descending.

Filter outside: window results can't be filtered in the same WHERE, so wrap them in a CTE.

Ties: ROW_NUMBER for exactly three rows; RANK to include everyone tied for third.

Sample spoken answer:

"I rank inside each class with a window function partitioned by class_id and ordered by score descending, inside a CTE, and then filter the rank in the outer query. I can't filter it in the same level because WHERE is worked out before window functions. The real decision is ties. If the rule is exactly three rows per class, I use ROW_NUMBER and add a tiebreaker, like the earlier submission time, so the result is repeatable. If two students tied for third should both appear, I use RANK and keep rank three or less, which can return four rows. DENSE_RANK would keep the top three distinct scores, which could mean many more people. I'd confirm which rule the person asking wants, because each one is a different answer."

Code:
WITH ranked AS (
  SELECT class_id, student_id, score,
         RANK() OVER (
           PARTITION BY class_id
           ORDER BY score DESC
         ) AS rnk
  FROM results
)
SELECT class_id, student_id, score, rnk
FROM ranked
WHERE rnk <= 3
ORDER BY class_id, rnk;
Red flag to avoid:

Trying to put the window function in WHERE, or picking ROW_NUMBER and quietly dropping a student who tied.

They may ask next:
  • How would you write this on a database with no window functions?
  • With millions of rows, what index would help this query?
Say it in 60 seconds
Medium Coding round Fresher, Mid-level Practice question

16. A readings table has sensor_id, read_at and reading. Show each reading next to the previous one for the same sensor, and the change.

What the interviewer is really testing:
Whether you can use LAG with the right partition and order, and handle the first row, which has no previous value.
Answer frame:

LAG: returns a value from an earlier row in the window; LEAD looks ahead.

Partition and order: per sensor, ordered by time, or you end up comparing across sensors.

First row: LAG gives NULL, so the change is NULL unless you pass a default.

Sample spoken answer:

"LAG looks back a set number of rows inside the window, one by default. I partition by sensor_id so each sensor is only compared with itself, and order by read_at so previous means earlier in time. Then the change is just the reading minus the lagged reading. For each sensor's first reading there's nothing before it, so LAG returns NULL and the change is NULL too, which I think is honest. If I wanted zero instead, LAG takes a third argument as a default, but that can hide the fact that there was no earlier reading. The mirror function, LEAD, looks forward, which is handy for working out how long each state lasted until the next one started."

Code:
SELECT sensor_id, read_at, reading,
       LAG(reading) OVER (
         PARTITION BY sensor_id ORDER BY read_at
       ) AS prev_reading,
       reading - LAG(reading) OVER (
         PARTITION BY sensor_id ORDER BY read_at
       ) AS reading_change
FROM readings
ORDER BY sensor_id, read_at;
Red flag to avoid:

Forgetting PARTITION BY, so the first reading of one sensor gets compared with the last reading of another.

They may ask next:
  • How would you find sensors whose reading jumped by more than 10 between two readings in a row?
  • What goes wrong if one sensor has two readings with the same timestamp?
Say it in 60 seconds
Hard Technical round Mid-level, Senior Practice question

17. Write a running total with a window function. Then tell me what the frame clause does and why ROWS and RANGE can give different totals.

What the interviewer is really testing:
Whether you know the default window frame and the tie behaviour that trips up running totals.
Answer frame:

Running total: SUM over a window ordered by date, partitioned if it should restart per group.

Default frame: with ORDER BY and no frame, the standard default is RANGE from the start to the current row, so rows tied on the order key share one total.

ROWS: counts physical rows, giving a strict row-by-row total; add a tiebreaker for a stable order.

Sample spoken answer:

"The running total is SUM of points over a window ordered by date, partitioned by account so it restarts for each one. The subtle part is the frame. If I write ORDER BY without a frame, the default is RANGE between unbounded preceding and current row. RANGE works on values, not rows, so if two rows have the same date they're peers, and both show the total including both of them. That surprises people when the running total jumps two rows at once. If I want a true row-by-row total, I write ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW and add a tiebreaker like id to the ORDER BY, so the order is repeatable. Frames are also how I do moving averages, for example the current row and the six before it."

Code:
SELECT account_id, entry_date, id, points,
       SUM(points) OVER (
         PARTITION BY account_id
         ORDER BY entry_date, id
         ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
       ) AS running_points
FROM points_ledger;
Red flag to avoid:

Not knowing there's a default frame, and being unable to explain why tied rows show the same running total.

They may ask next:
  • How would you build a seven-day moving average when some days have no rows at all?
  • What does SUM(points) OVER () give you, with nothing inside the brackets?
Say it in 60 seconds

Duplicates & NULLs 3 questions

Easy Coding round Fresher, Mid-level Practice question

18. A users table should have one row per email, but it doesn't. Write a query that finds the repeated emails and how many times each appears.

What the interviewer is really testing:
Whether you can use GROUP BY with HAVING for a basic data-quality check, and think about near-duplicates too.
Answer frame:

Group: by the value that should be unique.

Filter: HAVING COUNT(*) > 1 keeps only the repeated values.

Normalise: lower-case and trim if 'Ann@mail.com' and 'ann@mail.com ' are the same person.

Sample spoken answer:

"I group by email and keep the groups where COUNT(*) is greater than one, sorted by the count so the worst cases are on top. Then I'd ask whether case and spaces matter, because in real data the duplicates are often 'Ann@mail.com' and 'ann@mail.com' with a trailing space. If those should count as the same, I group by LOWER(TRIM(email)) instead. To see the actual rows rather than just the emails, I'd join back to users, or use COUNT(*) OVER (PARTITION BY email) and keep rows where it's above one. Once it's cleaned up, the lasting fix is a unique constraint, or a unique index on the lower-cased value where the engine supports that, so it can't happen again."

Code:
SELECT LOWER(TRIM(email)) AS email_key,
       COUNT(*) AS copies
FROM users
GROUP BY LOWER(TRIM(email))
HAVING COUNT(*) > 1
ORDER BY copies DESC;
Red flag to avoid:

Using DISTINCT to hide the duplicates instead of finding them, or ignoring case and spacing differences.

They may ask next:
  • How would you list every duplicate row with its id and created date?
  • How do you stop duplicates from being inserted again?
Say it in 60 seconds
Hard Coding round Mid-level, Senior Practice question

19. A users table has duplicate emails. Delete the extra rows but keep the oldest row for each email. How do you do it safely?

What the interviewer is really testing:
Whether you can pick exactly which rows to remove with a repeatable rule, and protect yourself before running a destructive statement.
Answer frame:

Pick the keeper: ROW_NUMBER per email, ordered by created date then id; row 1 stays.

Delete the rest: delete the ids whose row number is above 1.

Safety: run it as a SELECT first, check the count, use a transaction, and copy the rows you remove.

Sample spoken answer:

"First I make the rule repeatable. I number the rows within each email with ROW_NUMBER, ordered by created_at and then id, so even two rows created at the same moment have a clear winner. Row number 1 is the keeper, and I delete every id whose number is greater than 1. I leave out rows with no email, or all of them would count as one group. Before running the delete, I run the same query as a SELECT and check the count matches the duplicate report. I run it inside a transaction so I can roll back, and on a real system I copy the rows I'm deleting into a backup table first. I'd also check child rows, like orders that point at the ids I'm removing, and move them to the keeper before the delete."

Code:
DELETE FROM users
WHERE id IN (
  SELECT id
  FROM (
    SELECT id,
           ROW_NUMBER() OVER (
             PARTITION BY LOWER(TRIM(email))
             ORDER BY created_at, id
           ) AS rn
    FROM users
    WHERE email IS NOT NULL
  ) ranked
  WHERE rn > 1
);
Red flag to avoid:

Running a delete with no preview, no transaction and no clear rule for which copy survives.

They may ask next:
  • Other tables reference these user ids. What do you do with their rows before the delete?
  • The table has two hundred million rows. How would you run this without locking it for an hour?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level Practice question

20. Why does WHERE middle_name = NULL return nothing? Tell me how NULL behaves in comparisons, COUNT and other aggregates.

What the interviewer is really testing:
Whether you understand three-valued logic and how NULLs quietly change filters, counts and averages.
Answer frame:

Comparisons: anything compared with NULL is unknown, not true, so the row is filtered out; use IS NULL or IS NOT NULL.

Aggregates: SUM, AVG, MIN, MAX and COUNT(column) skip NULLs; COUNT(*) counts every row.

Tools: COALESCE for defaults; watch not-equal filters and averages.

Sample spoken answer:

"NULL means unknown, so NULL equals NULL isn't true, it's unknown, and WHERE only keeps rows where the condition is true. That's why = NULL finds nothing and I have to write IS NULL. The same logic hits not-equal: WHERE status <> 'closed' also drops rows where status is NULL, which is a common hidden bug. In aggregates, COUNT(*) counts every row, but COUNT(middle_name) only counts rows where it isn't NULL, and AVG ignores NULLs, so the average of 10, NULL and 20 is 15, not 10. If NULL should mean zero, I say so with COALESCE. Sorting also differs by engine: some put NULLs first and some last, so if it matters I write NULLS FIRST or NULLS LAST where that's supported."

Code:
SELECT COUNT(*)           AS all_rows,
       COUNT(middle_name) AS with_middle_name,
       AVG(bonus_points)  AS avg_skipping_nulls,
       AVG(COALESCE(bonus_points, 0)) AS avg_nulls_as_zero
FROM people;
Red flag to avoid:

Writing = NULL, or assuming AVG treats missing values as zero.

They may ask next:
  • What does NULL plus 5 return?
  • How would you make a filter like status <> 'closed' keep the rows where status is NULL?
Say it in 60 seconds

Schema & Objects 4 questions

Easy Technical round Fresher, Mid-level Practice question

21. What's the difference between DELETE, TRUNCATE and DROP? Which of them can you undo?

What the interviewer is really testing:
Whether you know what each command removes, how it behaves on a big table, and that rollback of TRUNCATE depends on the engine.
Answer frame:

DELETE: removes rows, can use WHERE, fires delete triggers, can be rolled back inside a transaction.

TRUNCATE: empties the whole table quickly, no WHERE; whether it can be rolled back depends on the engine.

DROP: removes the table itself: structure, indexes and data.

Sample spoken answer:

"DELETE works row by row. I can add a WHERE clause, it fires any delete triggers, each row change is logged, and inside a transaction I can roll it back. It's the slowest of the three on a big table. TRUNCATE empties the whole table in one go by releasing its storage, so it's much faster, but there's no WHERE, delete triggers don't fire, and it's often blocked if other tables point at it through foreign keys. Whether I can roll it back depends on the database: some treat it as a normal transactional statement, others commit it straight away. DROP removes the table itself, structure, indexes, permissions and all. So on a shared database I treat TRUNCATE and DROP as permanent unless I've checked the engine and I'm deliberately inside a transaction."

Red flag to avoid:

Saying TRUNCATE can never be rolled back, or always can be, instead of knowing it depends on the engine.

They may ask next:
  • You need to clear a staging table of fifty million rows every night. Which command would you use, and why?
  • Does TRUNCATE reset an auto-increment counter?
Say it in 60 seconds
Easy Technical round Fresher, Mid-level Practice question

22. What do primary key, unique, foreign key, NOT NULL and CHECK constraints each protect you from?

What the interviewer is really testing:
Whether you treat constraints as the database's last line of defence for data quality, not just as syntax.
Answer frame:

Primary key: one per table, unique and never NULL; it identifies each row.

Unique: no repeated values in a column or set of columns; how NULLs are treated varies by engine.

Foreign key: the value must exist in the parent table; ON DELETE decides what happens to the children.

NOT NULL and CHECK: the value must be present, and must pass a rule such as a score between 0 and 100.

Sample spoken answer:

"A primary key identifies each row: it's unique and can't be NULL, and a table has only one, though it can span several columns. A unique constraint also blocks repeats, like two accounts with the same email, but a table can have many of them, and how many NULLs it allows depends on the engine. A foreign key says an order's customer_id must exist in customers, and ON DELETE decides what happens when the parent is removed: block it, cascade the delete, or set the child's value to NULL. NOT NULL means a value must be there, and CHECK enforces a rule, say a score between 0 and 100. I like these in the database because application code has bugs and several apps may write to the same tables. The constraint catches it every time."

Code:
CREATE TABLE results (
  id         INT PRIMARY KEY,
  student_id INT NOT NULL,
  exam_code  VARCHAR(20) NOT NULL,
  score      INT CHECK (score BETWEEN 0 AND 100),
  UNIQUE (student_id, exam_code),
  FOREIGN KEY (student_id) REFERENCES students (id)
);
Red flag to avoid:

Saying constraints aren't needed because the application already validates everything.

They may ask next:
  • Should foreign key columns be indexed, and does the database always do that for you?
  • Would you enforce a rule like 'end date after start date' in the database or in the app?
Say it in 60 seconds
Medium Technical round Mid-level, Senior Practice question

23. What's the difference between a view, a stored procedure and a user-defined function, and when do you use each?

What the interviewer is really testing:
Whether you know what each database object is for, and can weigh putting logic in the database against keeping it in the app.
Answer frame:

View: a saved SELECT you query like a table; hides joins and limits which columns people see.

Function: takes inputs and returns a value or a table, and can be used inside a query.

Procedure: called on its own to run a series of statements, often with writes and transaction control.

Materialized view: stores the result and needs refreshing; not every engine has one.

Sample spoken answer:

"A view is a saved SELECT with a name. A normal view stores no data, so the query runs each time, and I use it to hide a messy join or to give a reporting user only certain columns. A materialized view actually stores the result, so it's fast to read but stale until it's refreshed, and not every engine has them. A user-defined function takes parameters and returns a value or a table, and I can call it inside a SELECT, like a function that formats a product code. Most engines limit what a function is allowed to change. A stored procedure is called on its own, with CALL or EXEC, and can run several statements, write data and often control transactions. I use procedures when logic must sit close to the data, but I keep business rules that change often in application code, where they're easier to test and ship."

Red flag to avoid:

Thinking a normal view keeps its own copy of the data, or not knowing that a function can be used inside a query and a procedure can't.

They may ask next:
  • Can you insert or update through a view? When does that work?
  • What are the downsides of putting most of the business logic in stored procedures?
Say it in 60 seconds
Hard Behavioral round Mid-level, Senior Practice question

24. Tell me about a schema change you made on a table that was in use. How did you keep it from breaking things?

What the interviewer is really testing:
Whether you think about locks, old code still running, backfills and a way back when you change a live table.
Answer frame:

The change: what and why, and how big and busy the table was.

The plan: add new columns first, backfill in batches, switch the code, clean up later.

Safety: tried on a copy first, a rollback path at every step, and locks watched while it ran.

Sample spoken answer:

"At my last company we had to split a full_name column into first and last name, on a users table with a few million rows that the app wrote to all day. Renaming or dropping the column in one step would have broken the running app. So we did it in stages. First we added the two new columns as nullable, which was quick. Then the app started writing to both the old and new columns. Next I backfilled the old rows in small batches, committing each batch so we never held long locks, and I watched replication lag while it ran. Once the data matched, which I checked with a query comparing counts and a sample of rows, we switched reads to the new columns. We dropped the old column weeks later in its own release, so we could roll back at any point before that."

Red flag to avoid:

Running one big ALTER and UPDATE on a busy table during the day, with no plan to undo it.

They may ask next:
  • Why add the new columns as nullable first, instead of NOT NULL straight away?
  • How did you test the migration before running it on the real table?
Say it in 60 seconds

Indexes & Performance 5 questions

Medium Technical round Fresher, Mid-level, Senior Practice question

25. What is an index, how does it make a query faster, and what does it cost you?

What the interviewer is really testing:
Whether you have a working model of a B-tree index, clustered versus non-clustered, and the trade-off on writes.
Answer frame:

Structure: usually a B-tree sorted by the key, so a lookup walks a few levels instead of scanning every row.

Clustered or not: a clustered index is the table stored in key order; others hold the key plus a pointer back to the row.

Cost: more storage, and every insert, update and delete has to maintain each index.

Sample spoken answer:

"An index is a separate sorted structure, usually a B-tree, built on one or more columns. Instead of scanning every row to find one email, the database walks down a few levels of the tree and lands on the right entries, and because it's sorted it also helps range filters and ORDER BY. In engines that use them, a clustered index is the table itself stored in key order, so there's only one. A non-clustered index keeps the key plus a pointer to the row, so the database may need an extra lookup to fetch the other columns. If the index already holds every column the query needs, it's covering and that lookup is skipped. The cost is on writes: each index takes space and must be updated on every insert, update and delete, so I add them for real query patterns, not just in case."

Code:
CREATE INDEX idx_orders_customer_date
  ON orders (customer_id, order_date);

-- Can use it: the leading column is filtered
SELECT id, order_date FROM orders
WHERE customer_id = 42 AND order_date >= '2025-01-01';

-- Usually can't seek on it: leading column is missing
SELECT id FROM orders WHERE order_date >= '2025-01-01';
Red flag to avoid:

Saying more indexes always make a database faster, with no mention of what they cost on writes.

They may ask next:
  • With an index on customer_id then order_date, why does column order matter?
  • Why might the optimizer ignore an index and scan the whole table anyway?
Say it in 60 seconds
Medium Coding round Mid-level, Senior Practice question

26. There's an index on created_at, but WHERE YEAR(created_at) = 2025 still scans the whole table. Why, and how do you rewrite it?

What the interviewer is really testing:
Whether you know that wrapping an indexed column in a function usually stops the database from seeking into the index.
Answer frame:

Cause: the index is sorted by created_at, not by YEAR(created_at), so the database must compute it for every row.

Rewrite: compare the bare column with a range, from the first day of the year up to but not including the next.

Same family: implicit type conversions, a LIKE pattern that starts with a wildcard, and maths on the column.

Sample spoken answer:

"The index is sorted on the raw created_at values. When I wrap the column in YEAR, the database has to calculate YEAR for every row before it can compare, so it can't jump into the index and it scans instead. The fix is to leave the column bare and move the logic to the other side: created_at on or after the first of January 2025 and before the first of January 2026. That half-open range also handles timestamps with a time part, which BETWEEN with an end date of the thirty-first would miss for anything after midnight. The same thing happens with a LIKE pattern that starts with a wildcard, with maths on the column, and with implicit conversions, like comparing a text column to a number. Some engines let you index the expression itself, but the rewrite is usually simpler."

Code:
-- Can't seek: function wrapped around the column
SELECT id FROM orders WHERE YEAR(created_at) = 2025;

-- Can seek: bare column, half-open range
SELECT id FROM orders
WHERE created_at >= '2025-01-01'
  AND created_at <  '2026-01-01';
Red flag to avoid:

Suggesting a bigger server or yet another index when the real problem is the function wrapped around the column.

They may ask next:
  • How would you make a case-insensitive search on email use an index?
  • A text column holds numbers and the query compares it to a number. What can happen to the index?
Say it in 60 seconds
Medium Technical round Mid-level, Senior Practice question

27. How do you read an execution plan? When a query is slow, what do you look for first?

What the interviewer is really testing:
Whether you use the plan as evidence, spotting scans, bad row estimates and costly joins, instead of guessing at fixes.
Answer frame:

Get the real plan: plain EXPLAIN shows estimates; the version that runs the query, such as EXPLAIN ANALYZE or an actual plan, shows real rows and time.

Look for: full scans on big tables, estimated rows far from actual rows, and the step where most time goes.

Join and sort steps: a nested loop repeated many times, lookups back to the table for every row, and sorts that spill to disk.

Act: fix the biggest cost, re-run, and compare the two plans.

Sample spoken answer:

"I get the plan the engine actually used, not just the estimate. Plain EXPLAIN only shows what the optimizer expects, while the version that really runs the query shows real row counts and time for each step. I read it from the innermost steps outwards, since that's where the rows come from, and find the step that takes most of the time. The first things I check are full scans on big tables where I expected an index, and steps where the estimated rows are far from the actual rows. A bad estimate usually means stale statistics or a filter the optimizer can't reason about, and it leads to wrong choices, like a nested loop that runs thousands of times. I also look for big sorts spilling to disk. Then I fix the biggest cost, re-run it and compare the two plans."

Code:
-- PostgreSQL: runs the query, then shows real rows and timing
EXPLAIN ANALYZE
SELECT id, order_date
FROM orders
WHERE customer_id = 42
ORDER BY order_date DESC;
Red flag to avoid:

Never having opened a plan, or reading only the total cost figure without comparing estimated and actual rows.

They may ask next:
  • The plan shows an index being used, but the query is still slow. What could be going on?
  • The actual-run plan executes the statement. How would you use it safely on an UPDATE or DELETE?
Say it in 60 seconds
Medium Behavioral round Mid-level, Senior Practice question

28. Tell me about a slow SQL query you made faster. How did you find out where the time was going?

What the interviewer is really testing:
Whether you diagnose with evidence like the query plan instead of guessing, and whether you checked the fix held up.
Answer frame:

Situation: which query, how slow, and who felt it.

Diagnosis: the plan, estimated rows against actual rows, and where most of the time went.

Fix and proof: the change, the before and after timing, and the side effects you checked.

Sample spoken answer:

"At my last company a search page timed out for our largest customers. The query joined orders to order_items and filtered by customer and date. I ran it with the actual execution plan and saw a full scan on order_items, and the estimated row counts were tiny compared with the real ones, so the optimizer had picked a plan that was fine for small customers and awful for big ones. Two things were wrong: there was no index on order_items.order_id, and the date filter wrapped the column in a function. I added the index and rewrote the filter as a plain range, and the query went from about forty seconds to under one second for the biggest customer. I checked that inserts into order_items didn't slow down, and added the query to our slow-query alerts so we'd catch it if it drifted again."

Red flag to avoid:

A story where the fix was a guess, like 'I added some indexes', with no plan, no numbers and no check afterwards.

They may ask next:
  • How did you know the fix didn't slow down writes?
  • What would you have done if adding an index wasn't allowed?
Say it in 60 seconds
Medium Situational round Mid-level, Senior Practice question

29. A report query ran in two seconds for months, and now it takes three minutes. Nobody changed the code. How do you work out what happened?

What the interviewer is really testing:
Whether you check what changes under unchanged SQL, like data volume, statistics, plans and blocking, in a sensible order instead of guessing.
Answer frame:

Query or server: check for blocking locks, a busy server or a heavy job running at the same time.

Compare plans: get today's plan and, if the engine keeps history, the old one, and see what changed.

Usual causes: data growth, stale statistics, a dropped index, or a cached plan built for an unusual parameter value.

Fix and guard: fix the cause, confirm with a new plan and timing, and add an alert on run time.

Sample spoken answer:

"Same SQL doesn't mean same work, so I'd look at what changed around it. First I'd check whether it's slow all the time or only at certain hours, and whether it's just waiting on locks from another job, because then the query itself isn't the problem. Next I'd get the current execution plan and compare it with an older one if the engine keeps plan history. Usually the plan has flipped. Maybe the table grew until the optimizer switched to a scan, the statistics are out of date so the row estimates are wrong, someone dropped an index, or the cached plan was built for an unusual parameter value. Depending on which it is, I'd refresh statistics, put the right index back, or deal with the parameter issue, then confirm with a new plan and timing. Finally I'd add an alert on its run time, so we hear about it before users do."

Red flag to avoid:

Jumping straight to a new index or a bigger server without checking the plan, the statistics or blocking.

They may ask next:
  • How would you tell whether the query is slow or just waiting on a lock?
  • How can a cached plan make the same query fast for one customer and slow for another?
Say it in 60 seconds

Transactions 3 questions

Medium Coding round Fresher, Mid-level, Senior Practice question

30. What does ACID mean? Show me how you'd move 100 points from one account to another so it can never half-happen.

What the interviewer is really testing:
Whether you can tie each ACID property to a real failure, and write a transaction that checks what it actually changed.
Answer frame:

Atomic: both updates happen, or neither does.

Consistent and isolated: the rules hold at the end, and other transactions don't see half-done work.

Durable: once committed, it survives a crash.

In practice: begin, guarded updates, check rows affected, then commit or roll back.

Sample spoken answer:

"Atomicity means the whole transaction happens or none of it does, so I can't take points from A without giving them to B. Consistency means the rules, like a CHECK that a balance never goes below zero, still hold when it's done. Isolation means other transactions don't see my half-finished state. Durability means that once the commit comes back, a crash won't lose it. For the transfer, I begin a transaction and subtract from the sender only where their balance is at least 100, then check that exactly one row was updated. If not, I roll back. Then I add to the receiver and commit. If anything fails in between, I roll back and nothing has changed. Putting the balance check inside the UPDATE avoids a race where two transfers both read the old balance."

Code:
BEGIN;  -- BEGIN TRANSACTION in SQL Server

UPDATE accounts
SET balance = balance - 100
WHERE id = 1 AND balance >= 100;
-- the app checks exactly 1 row changed, else ROLLBACK

UPDATE accounts
SET balance = balance + 100
WHERE id = 2;

COMMIT;
Red flag to avoid:

Reciting the four words without being able to say what goes wrong when one of them is missing.

They may ask next:
  • Two transfers touch the same two accounts in opposite order at the same moment. What can happen?
  • Where does the rows-updated check live: in SQL or in the application?
Say it in 60 seconds
Hard Technical round Mid-level, Senior Practice question

31. Explain dirty reads, non-repeatable reads and phantom reads. Which isolation levels prevent which?

What the interviewer is really testing:
Whether you know the standard isolation levels, and that real engines implement them differently and pick different defaults.
Answer frame:

Dirty read: seeing another transaction's uncommitted change; prevented from Read Committed up.

Non-repeatable read: the same row read twice gives different values; prevented from Repeatable Read up.

Phantom read: a range query run twice returns new rows; the standard only guarantees this is prevented at Serializable.

Trade-off: stronger levels mean more locking or more retries, so defaults sit lower.

Sample spoken answer:

"A dirty read is seeing data another transaction changed but hasn't committed, so if it rolls back, I acted on something that never existed. A non-repeatable read is when I read a row, someone commits an update, and my second read of the same row differs. A phantom is re-running a query like all orders today and getting extra rows someone just inserted. The standard levels go Read Uncommitted, which allows all three, Read Committed, which stops dirty reads, Repeatable Read, which also stops non-repeatable reads, and Serializable, which stops phantoms too and behaves as if transactions ran one at a time. Real engines differ: many default to Read Committed, MySQL with InnoDB defaults to Repeatable Read, and some use snapshots rather than locks. So I check the engine's documentation before relying on a level."

Red flag to avoid:

Saying a higher isolation level is always better, or not knowing which anomaly each level still allows.

They may ask next:
  • What is a lost update, and which level or technique prevents it?
  • Why might a Serializable transaction fail and need a retry, even with no bug in your code?
Say it in 60 seconds
Hard Situational round Mid-level, Senior Practice question

32. You run an UPDATE on production and it reports 80,000 rows changed when you expected 12. What do you do in the next few minutes?

What the interviewer is really testing:
Whether you stay calm, use the open transaction if there is one, tell people quickly, and restore the data without making it worse.
Answer frame:

If still open: roll back straight away; that is why manual changes run in a transaction.

If committed: stop further damage and tell your lead and whoever is on call now; don't hide it.

Recover: restore a copy from backup or point-in-time recovery into a separate database, then copy back only the affected rows.

Prevent: preview with SELECT, check row counts, and use a transaction for every manual change.

Sample spoken answer:

"The first question is whether the transaction is still open. If I ran it inside BEGIN, which I always try to do on production, I see the count is wrong and I roll back, and nothing has happened. If it already committed, I stop and tell my lead and whoever's on call straight away, because the sooner people know, the more options we have, and users might be affected right now. Then we work out recovery. If there's point-in-time restore, we restore a copy to a separate database from just before my update and copy the old values back for exactly those rows, rather than restoring over live data. Afterwards I'd write up what happened honestly and push for a habit change: run the WHERE as a SELECT first, check the count, and use a transaction for every manual change."

Red flag to avoid:

Trying to fix it quietly alone with another hurried UPDATE, or not telling anyone.

They may ask next:
  • There is no backup newer than last night. What are your options?
  • How would you make this kind of mistake harder for the whole team?
Say it in 60 seconds
Were you asked something else? Share it A person checks every question before it goes on the site. No name is shown.
For the call itself

The questions above are the prep. The call has ten more.

ClapAssist is an AI interview assistant for Mac and Windows. It listens to the interview on your computer and shows you what to say, in short lines you can read while you talk. Your resume and notes are never stored on our servers. It stays out of screen share on every plan; only you can see it.

Download ClapAssist with 10 free minutes
Mac and Windows · Stays out of screen share · No card